Module music_df.xml_parser.repeats
Functions
def get_repeat_segments(repeats: Dict[int, Dict[str, Dict[str, Any]]],
measure_ends: Dict[int, numbers.Number],
warn: bool = False) ‑> Tuple[List[Tuple[numbers.Number, numbers.Number]], List[Tuple[numbers.Number, numbers.Number]], List[str]]-
Expand source code
def get_repeat_segments( repeats: t.Dict[int, t.Dict[str, t.Dict[str, t.Any]]], measure_ends: t.Dict[int, Number], warn: bool = False, ) -> t.Tuple[ t.List[t.Tuple[Number, Number]], t.List[t.Tuple[Number, Number]], t.List[str], ]: """ Args: repeats: a dictionary: - keys are integers indicating bar numbers. (Expected to be 1-indexed.) - values are dictionaries storing repeat attributes where: - keys indicate the repeat type ("backward", "forward", or "start-ending") - values are a dict that stores the other attributes of the repeat (e.g., "times") measure_ends: a dictionary: - keys are integers indicating bar numbers. (Expected to be 1-indexed.) - values are Fractions indicating the onset time of the closing barline of each measure Returns: a 3-tuple of "orig_segments", "repeated_segments", and "segment_types". The first two items are lists of "start" and "end" times. The third item is a list of strings. >>> measure_ends = {i + 1: (i + 1) * 4 for i in range(16)} >>> get_repeat_segments({}, measure_ends) ([(0, inf)], [(0, inf)], ['no_repeat']) >>> get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat']) Note that "times" attribute of forward repeats is ignored. >>> get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 3}}}, ... measure_ends) ([(0, 8), (0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, 24), (24, inf)], ['simple_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat']) Repeat of a single measure: >>> get_repeat_segments( ... {1: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) ([(0, 4), (0, 4), (4, inf)], [(0, 4), (4, 8), (8, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat']) First forward repeat not at start. >>> get_repeat_segments( ... {2: {"forward": {"times": 2}}, ... 3: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 4), (4, 12), (4, 12), (12, inf)], [(0, 4), (4, 12), (12, 20), (20, inf)], ['no_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat']) Multiple repeats. >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}, ... 3: {"forward": {"times": 2}}, ... 6: {"backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 8), (0, 8), (8, 24), (8, 24), (24, inf)] >>> segment_types ['simple_repeat', 'simple_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat'] Repeats with a gap. >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}, ... 4: {"forward": {"times": 2}}, ... 6: {"backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 8), (0, 8), (8, 12), (12, 24), (12, 24), (24, inf)] >>> segment_types ['simple_repeat', 'simple_repeat', 'no_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat'] Endings. >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 3: {"start-ending": {"number": "2"}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2'] Gap between endings and next repeat. >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 3: {"start-ending": {"number": "2"}}, ... 5: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, 16), (16, 20), (16, 20), (20, inf)] >>> segment_types # NB that all music after the start of the second ending goes into 'ending_2' ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat'] >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 3}}, ... 3: {"start-ending": {"number": "2"}}, ... 5: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (4, 8), (0, 4), (8, 16), (16, 20), (16, 20), (20, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat'] Missing endings after a backward repeat are inferred (note that the second ending here is missing): >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2'] >>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 4: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, 12), (12, 16), (12, 16), (16, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat'] The "number" attribute of the endings doesn't influence `orig_segments` or `repeated_segments`. It is included in `segment_types`, however. >>> orig_segments, _, segment_types = get_repeat_segments( ... {2: {"forward": {"times": 2}}, ... 3: {"start-ending": {"number": "1, 2"}, "backward": {"times": 3}}, ... 4: {"start-ending": {"number": "3"}, "backward": {"times": 2}}, ... 5: {"start-ending": {"number": "4"}}, ... 6: {"forward": {"times": 2}}, ... 8: {"start-ending": {"number": "1"}}, ... 9: {"backward": {"times": 2}}, ... 10: {"start-ending": {"number": "2"}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (8, 12), (4, 8), (8, 12), (4, 8), (12, 16), (4, 8), (16, 20), (20, 28), (28, 36), (20, 28), (36, inf)] >>> segment_types ['no_repeat', 'pre_ending_repeat', 'ending_1, 2', 'pre_ending_repeat', 'ending_1, 2', 'pre_ending_repeat', 'ending_3', 'pre_ending_repeat', 'ending_4', 'pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2'] We infer missing forward repeats: >>> get_repeat_segments( ... {2: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat']) >>> get_repeat_segments( ... {2: {"backward": {"times": 2}}, ... 4: {"backward": {"times": 2}}}, ... measure_ends)[0] [(0, 8), (0, 8), (8, 16), (8, 16), (16, inf)] Missing final repeat: >>> get_repeat_segments( ... {1: {"forward": {"times": 2}}}, ... measure_ends) ([(0, 64), (0, 64)], [(0, 64), (64, 128)], ['simple_repeat', 'simple_repeat']) """ if not repeats: return ([(0, float("inf"))], [(0, float("inf"))], ["no_repeat"]) assert min(repeats) > 0 assert min(measure_ends) > 0 last_forward_repeat = 0 last_backward_repeat = 0 last_boundary = lambda: max(last_forward_repeat, last_backward_repeat) ending_jump_from = None segments = [] segment_types = [] ending_number = "" ending_number_used = True for measure_num in sorted(repeats.keys()): repeat_dict = repeats[measure_num] if "forward" in repeat_dict: if "start-ending" in repeat_dict: if warn: warnings.warn( f"M. {measure_num} has 'forward' repeat bar and " "'start-ending'; ignoring 'start-ending'" ) onset = _get_onset_from_measure_num(measure_num - 1, measure_ends) if ending_jump_from is not None: # there are incomplete endings... segments.extend( [ (last_forward_repeat, ending_jump_from), (ending_jump_to, onset), ] ) if ending_number_used: ending_number = _update_ending_number(ending_number, warn=warn) segment_types.extend( ["pre_ending_repeat", f"ending_{ending_number}"] ) ending_jump_from = None last_forward_repeat = onset elif "start-ending" in repeat_dict: onset = _get_onset_from_measure_num(measure_num - 1, measure_ends) if ending_jump_from is None: ending_jump_from = onset ending_jump_to = onset ending_number = repeat_dict["start-ending"]["number"] ending_number_used = False if "backward" in repeat_dict: onset = _get_onset_from_measure_num(measure_num, measure_ends) if not segments: if last_forward_repeat != 0: segments.append((0, last_forward_repeat)) segment_types.append("no_repeat") if ending_jump_from is not None: for _ in range(repeat_dict["backward"]["times"] - 1): segments.extend( [ (last_forward_repeat, ending_jump_from), (ending_jump_to, onset), ] ) segment_types.extend( ["pre_ending_repeat", f"ending_{ending_number}"] ) ending_number_used = True # we need to set "ending_jump_to" here to catch the case where # there is an omitted 2nd ending after a first ending ending_jump_to = onset else: if last_forward_repeat < last_backward_repeat: # there is a missing forward repeat last_forward_repeat = last_backward_repeat if segments and last_forward_repeat != segments[-1][1]: segments.append((segments[-1][1], last_forward_repeat)) segment_types.append("no_repeat") for _ in range(repeat_dict["backward"]["times"]): segments.append((last_forward_repeat, onset)) segment_types.append("simple_repeat") last_backward_repeat = onset if ending_jump_from is not None: segments.extend( [ (last_forward_repeat, ending_jump_from), (ending_jump_to, float("inf")), ] ) if ending_number_used: ending_number = _update_ending_number(ending_number, warn=warn) segment_types.extend(["pre_ending_repeat", f"ending_{ending_number}"]) if last_forward_repeat == last_boundary(): # There is a missing backward repeat at the end of the score. segments.extend( [(last_forward_repeat, measure_ends[max(measure_ends)])] * 2 ) segment_types.extend(["simple_repeat", "simple_repeat"]) if segments[-1][1] < measure_ends[max(measure_ends)]: segments.append((last_boundary(), float("inf"))) segment_types.append("no_repeat") segment_durs = [segment[1] - segment[0] for segment in segments] segment_ends = list(itertools.accumulate(segment_durs)) segment_starts = [0] + segment_ends[:-1] return segments, list(zip(segment_starts, segment_ends)), segment_typesArgs
repeats- a dictionary: - keys are integers indicating bar numbers. (Expected to be 1-indexed.) - values are dictionaries storing repeat attributes where: - keys indicate the repeat type ("backward", "forward", or "start-ending") - values are a dict that stores the other attributes of the repeat (e.g., "times")
measure_ends- a dictionary: - keys are integers indicating bar numbers. (Expected to be 1-indexed.) - values are Fractions indicating the onset time of the closing barline of each measure
Returns
a 3-tuple of "orig_segments", "repeated_segments", and "segment_types". The first two items are lists of "start" and "end" times. The third item is a list of strings.
>>> measure_ends = {i + 1: (i + 1) * 4 for i in range(16)}>>> get_repeat_segments({}, measure_ends) ([(0, inf)], [(0, inf)], ['no_repeat'])>>> get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat'])Note that "times" attribute of forward repeats is ignored.
>>> get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 3}}}, ... measure_ends) ([(0, 8), (0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, 24), (24, inf)], ['simple_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat'])Repeat of a single measure:
>>> get_repeat_segments( ... {1: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) ([(0, 4), (0, 4), (4, inf)], [(0, 4), (4, 8), (8, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat'])First forward repeat not at start.
>>> get_repeat_segments( ... {2: {"forward": {"times": 2}}, ... 3: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 4), (4, 12), (4, 12), (12, inf)], [(0, 4), (4, 12), (12, 20), (20, inf)], ['no_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat'])Multiple repeats.
>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}, ... 3: {"forward": {"times": 2}}, ... 6: {"backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 8), (0, 8), (8, 24), (8, 24), (24, inf)] >>> segment_types ['simple_repeat', 'simple_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat']Repeats with a gap.
>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"backward": {"times": 2}}, ... 4: {"forward": {"times": 2}}, ... 6: {"backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 8), (0, 8), (8, 12), (12, 24), (12, 24), (24, inf)] >>> segment_types ['simple_repeat', 'simple_repeat', 'no_repeat', 'simple_repeat', 'simple_repeat', 'no_repeat']Endings.
>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 3: {"start-ending": {"number": "2"}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2']Gap between endings and next repeat.
>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 3: {"start-ending": {"number": "2"}}, ... 5: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, 16), (16, 20), (16, 20), (20, inf)] >>> segment_types # NB that all music after the start of the second ending goes into 'ending_2' ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat']>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 3}}, ... 3: {"start-ending": {"number": "2"}}, ... 5: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (4, 8), (0, 4), (8, 16), (16, 20), (16, 20), (20, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat']Missing endings after a backward repeat are inferred (note that the second ending here is missing):
>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2']>>> orig_segments, _, segment_types = get_repeat_segments( ... {1: {"forward": {"times": 2}}, ... 2: {"start-ending": {"number": "1"}, "backward": {"times": 2}}, ... 4: {"forward": {"times": 2}, "backward": {"times": 2}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (0, 4), (8, 12), (12, 16), (12, 16), (16, inf)] >>> segment_types ['pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2', 'simple_repeat', 'simple_repeat', 'no_repeat']The "number" attribute of the endings doesn't influence
orig_segmentsorrepeated_segments. It is included insegment_types, however.>>> orig_segments, _, segment_types = get_repeat_segments( ... {2: {"forward": {"times": 2}}, ... 3: {"start-ending": {"number": "1, 2"}, "backward": {"times": 3}}, ... 4: {"start-ending": {"number": "3"}, "backward": {"times": 2}}, ... 5: {"start-ending": {"number": "4"}}, ... 6: {"forward": {"times": 2}}, ... 8: {"start-ending": {"number": "1"}}, ... 9: {"backward": {"times": 2}}, ... 10: {"start-ending": {"number": "2"}}}, ... measure_ends) >>> orig_segments [(0, 4), (4, 8), (8, 12), (4, 8), (8, 12), (4, 8), (12, 16), (4, 8), (16, 20), (20, 28), (28, 36), (20, 28), (36, inf)] >>> segment_types ['no_repeat', 'pre_ending_repeat', 'ending_1, 2', 'pre_ending_repeat', 'ending_1, 2', 'pre_ending_repeat', 'ending_3', 'pre_ending_repeat', 'ending_4', 'pre_ending_repeat', 'ending_1', 'pre_ending_repeat', 'ending_2']We infer missing forward repeats:
>>> get_repeat_segments( ... {2: {"backward": {"times": 2}}}, ... measure_ends) ([(0, 8), (0, 8), (8, inf)], [(0, 8), (8, 16), (16, inf)], ['simple_repeat', 'simple_repeat', 'no_repeat'])>>> get_repeat_segments( ... {2: {"backward": {"times": 2}}, ... 4: {"backward": {"times": 2}}}, ... measure_ends)[0] [(0, 8), (0, 8), (8, 16), (8, 16), (16, inf)]Missing final repeat:
>>> get_repeat_segments( ... {1: {"forward": {"times": 2}}}, ... measure_ends) ([(0, 64), (0, 64)], [(0, 64), (64, 128)], ['simple_repeat', 'simple_repeat'])