Source code for musif.musicxml.repeat

import itertools
from typing import List

from music21.bar import Repeat
from music21.chord import Chord
from music21.meter import TimeSignature
from music21.note import Note, Rest
from music21.repeat import RepeatMark
from music21.spanner import RepeatBracket, Slur
from music21.stream import Measure, Part, Score

from musif.logs import ldebug

# TODO: document this module


[docs]def measure_ranges( instrument_measures, init, end, iteration=None, offset=None, twoCompasses=False, remove_repetition_marks=False, ): measures = [] o = offset last_offset = ( 0.0 if int(init) - 6 < 0 else instrument_measures[int(init) - 6].offset ) # Find index where measureNumber init and end is stored init_index = instrument_measures.index( [m for m in instrument_measures if m.measureNumber == init][0] ) end_compass = [m for m in instrument_measures if m.measureNumber == end] end_index = ( instrument_measures.index(end_compass[0]) if len(end_compass) > 0 else len(instrument_measures) - 1 ) for i in range(init_index, end_index + 1): if ( not i < 0 and i < len(instrument_measures) and instrument_measures[i].measureNumber >= int(init) and instrument_measures[i].measureNumber <= int(end) ): if not twoCompasses: compass = instrument_measures[i].quarterLength m = Measure(number=instrument_measures[i].measureNumber) if remove_repetition_marks: m.elements = [ e for e in instrument_measures[i].elements if not isinstance(e, RepeatMark) ] else: m.elements = instrument_measures[i].elements m.quarterLength = compass if offset is None: m.offset = last_offset else: m.offset = o o += compass measures.append(m) if ( instrument_measures[i].measureNumber != 0.0 and instrument_measures[i].offset != 0 ): last_offset = instrument_measures[i].offset + compass twoCompasses = False """if iteration is 1: measures = measures[:-1] elif iteration is 2: measures = measures[:-2] + [measures[-1]]""" if iteration == 2: last_measure = instrument_measures[i + 1] last_measure.offset = measures[-1].offset measures = measures[:-1] + [last_measure] return measures
# TODO: This function is too long
[docs]def expand_repeat_bars(score): final_score = Score() final_score.metadata = score.metadata exist_repetition_bars = False # find repeat bars and expand for instr in score.parts: part_measures = get_measures_with_repetitions( instr.elements ) # returns the measures with repetitions last_measure = part_measures[-1].measureNumber part_measures_expanded = [] startsin0 = part_measures[0].measureNumber == 0 # Everything should be -1 repetition_bars = [] # Find all repetitions in that instrument for elem in instr.elements: if isinstance(elem, Measure): for e in elem: if isinstance(e, Repeat): exist_repetition_bars = True if e.direction == "start": repetition_bars.append((e.measureNumber, "start")) elif e.direction == "end": repetition_bars.append((e.measureNumber, "end")) index = elem.elements.index(e) elem.elements = ( elem.elements[:index] + elem.elements[index + 1 :] ) elif isinstance(elem, RepeatBracket): string_e = str(elem) index = string_e.find("music21.stream.Measure") measure = ( string_e[index:].replace("music21.stream.Measure", "")[1:3].strip() ) repetition_bars.append((int(measure), elem.number)) index = instr.elements.index(elem) elem.elements = instr.elements[:index] + instr.elements[index + 1 :] repetition_bars = sorted(list(repetition_bars), key=lambda tup: tup[0]) start = 0 if startsin0 else 1 if exist_repetition_bars: p = Part() p.id = instr.id p.partName = instr.partName for rb in repetition_bars: compass = measure_ranges(part_measures, rb[0], rb[0])[0].quarterLength if rb[1] == "start": if len(part_measures_expanded) > 0: offset = part_measures_expanded[-1][-1].offset else: offset = 0 start_measures = measure_ranges( part_measures, start, rb[0] - 1, offset=offset + compass ) if len(start_measures) > 0: part_measures_expanded.append(start_measures) start = rb[0] elif rb[1] == "end": if len(part_measures_expanded) > 0: offset = part_measures_expanded[-1][-1].offset else: offset = 0 casilla_1 = ( True if any( re[1] == "1" and re[0] <= rb[0] for re in repetition_bars ) else False ) casilla_2 = None if casilla_1: casilla_2 = [ re for re in repetition_bars if re[1] == "2" and re[0] > rb[0] ] casilla_2 = None if len(casilla_2) == 0 else casilla_2[0] part_measures_expanded.append( measure_ranges( part_measures, init=start, end=rb[0], offset=offset + compass, remove_repetition_marks=True, ) ) # This should erase the repetition marks if casilla_2 is not None: part_measures_expanded.append( measure_ranges( part_measures, start, casilla_2[0], iteration=2, offset=part_measures_expanded[-1][-1].offset + compass, ) ) start = casilla_2[0] + 1 else: part_measures_expanded.append( measure_ranges( part_measures, init=start, end=rb[0], offset=part_measures_expanded[-1][-1].offset + compass, ) ) start = rb[0] + 1 if start < last_measure: compass = measure_ranges(part_measures, start, start + 1)[ 0 ].quarterLength offset = part_measures_expanded[-1][-1].offset part_measures_expanded.append( measure_ranges( part_measures, start, last_measure + 1, offset=offset + compass ) ) p.elements = list(itertools.chain(*tuple(part_measures_expanded))) final_score.insert(0, p) return final_score if exist_repetition_bars else score
[docs]def slur_processing(part): slurs = [s for s in part.elements if isinstance(s, Slur)] part_measures = get_measures_with_repetitions( part.elements ) # returns the measures with repetitions for slur in slurs: slur_notes = slur.getSpannedElements() first_note_measure = slur_notes[0].measureNumber first_note_offset = slur_notes[0].offset last_note_measure = slur_notes[-1].measureNumber last_note_offset = slur_notes[-1].offset # Buscar ese compas y offset y recorrer desde esa nota hasta la siguiente implied_measures = measure_ranges( part_measures, first_note_measure, last_note_measure ) first_found = False last_found = False for measure in implied_measures: notes = measure.elements i = 0 while i < len(notes) and not last_found: if isinstance(notes[i], Note): if notes[i].offset == first_note_offset: first_found = True elif notes[i].offset == last_note_offset: last_found = True elif first_found and not last_found: slur.addSpannedElements(notes[i]) i += 1
[docs]def expand_score_repetitions(score, repeat_elements): score = expand_repeat_bars(score) # FIRST EXPAND REPEAT BARS final_score = Score() final_score.metadata = score.metadata if len(repeat_elements) > 0: for part in score.parts: p = expand_part(part, repeat_elements) final_score.insert(0, p) else: final_score = score return final_score
# TODO: this function seems alittle long as well
[docs]def get_expanded_measures(part_measures, repeat_elements): repeat_bracket = ( sum([1 if "repeat bracket" in item[1] else 0 for item in repeat_elements]) > 0 ) measures_list = [] startsin0 = part_measures[0].measureNumber == 0 # Everything should be -1 there_is_fine = False there_is_segno = False # 1. find the fine and segno if any([r[1] == "fine" for r in repeat_elements]): f = [x[0] for x in repeat_elements if x[1] == "fine"][0] there_is_fine = True if any([r[1] == "segno" for r in repeat_elements]): s = [x[0] for x in repeat_elements if x[1] == "segno"][0] there_is_segno = True # 2. Having all the repetition elements, get the measures if there_is_segno: # Introduction before_segno = measure_ranges(part_measures, 1 if not startsin0 else 0, s - 1) measures_list.append( before_segno ) # S -1 OR S-> when segno in compass 1, s, else s-1? dc_time_signature = [ y for x in before_segno for y in x.elements if isinstance(y, TimeSignature) ] elif there_is_fine: measures_list.append( measure_ranges( part_measures, 1 if not startsin0 else 0, f - 1, iteration=1 if repeat_bracket else None, ) ) else: measures_list.append( measure_ranges( part_measures, 1 if not startsin0 else 0, len(part_measures), iteration=1 if repeat_bracket else None, ) ) for repeat in repeat_elements: repeat_measure = measure_ranges(part_measures, repeat[0], repeat[0]) compass = repeat_measure[0].quarterLength if repeat[1] == "segno": offset = measures_list[-1][-1].offset segno_part = measure_ranges( part_measures, s, f - 1 if there_is_fine else len(part_measures), iteration=1 if repeat_bracket else None, offset=offset + compass, remove_repetition_marks=True, ) measures_list.append(segno_part) # Segno to Fine elif repeat[1] == "fine": twoCompasses = False """if len(repeat_measure) > 0: twoCompasses = True""" offset = measures_list[-1][-1].offset fine_part = measure_ranges( part_measures, f, len(part_measures), offset=offset + compass, twoCompasses=twoCompasses, remove_repetition_marks=True, ) measures_list.append(fine_part) # Fine to end elif repeat[1] == "al segno" or repeat[1] == "dal segno": offset = measures_list[-1][-1].offset # segnos' compass time signature segno_time_measure = [ x for x in segno_part[0].elements if isinstance(x, TimeSignature) ] segno_time_measure = ( segno_time_measure if len(segno_time_measure) != 0 else dc_time_signature[-1] ) alsegno_list = measure_ranges( part_measures, s, f - 1 if there_is_fine else len(part_measures), iteration=2 if repeat_bracket else None, offset=offset + compass, remove_repetition_marks=True, ) if not any(isinstance(x, TimeSignature) for x in alsegno_list[0].elements): # we reset the time signature that was on the dacapo alsegno_list[0].elements = tuple( [segno_time_measure] + list(alsegno_list[0].elements) ) measures_list.append(alsegno_list) # Segno to fine elif repeat[1] == "da capo": offset = measures_list[-1][-1].offset if startsin0 and there_is_fine and not repeat_bracket: f += 1 dacapo_list = measure_ranges( part_measures, 0 if startsin0 else 1, f - 1 if there_is_fine else len(part_measures), iteration=2 if repeat_bracket else None, offset=offset + compass, remove_repetition_marks=True, ) measures_list.append(dacapo_list) return measures_list
[docs]def get_measures_with_repetitions(obj) -> List[Measure]: measures = [] for elem in obj: if isinstance(elem, Measure): measures.append(elem) # Change the note offsets to avoid problems due to slurs last_offset = 0 last_duration = 0 for note in elem: if ( isinstance(note, Note) or isinstance(note, Rest) or isinstance(note, Chord) ): note.offset = last_offset + last_duration last_offset = note.offset last_duration = note.duration.quarterLength return measures
[docs]def expand_part(part: Part, repeat_elements): part_measures = get_measures_with_repetitions( part.elements ) # returns measures with repetitions p = Part() p.id = part.id p.partName = part.partName part_measures_expanded = get_expanded_measures( part_measures, repeat_elements ) # returns the measures expanded part_measures_expanded = list(itertools.chain(*part_measures_expanded)) # part_measures_expanded = sorted(tuple(part_measures_expanded), key =lambda x: x.offset) # Assign a new continuous compass number to every measure measure_number = 0 if part_measures_expanded[0].measureNumber == 0 else 1 for i, e in enumerate(part_measures_expanded): m = Measure(number=measure_number) m.elements = e.elements m.offset = e.offset m.quarterLength = e.quarterLength part_measures_expanded[i] = m measure_number += 1 p.elements = part_measures_expanded return p
[docs]def get_repetition_elements(score: Score, verbose=True) -> List[tuple]: # 1. Get the repeat elements repeat_elements = set() for part in score.parts: instr_repeat_elements = [] for elem in part.elements: if isinstance(elem, Measure): for e in elem: if isinstance(e, RepeatMark) and not isinstance(e, Repeat): measure = e.measureNumber if elem.numberSuffix in ["X1", "X2"]: # Exception measure += 1 instr_repeat_elements.append((measure, e.name)) elif isinstance(elem, RepeatBracket): string_e = str(elem) index = string_e.find("music21.stream.Measure") string_e = string_e[index:].replace("music21.stream.Measure ", "") measure = ( string_e.split(" ")[0].strip().replace("X1", "").replace("X2", "") ) instr_repeat_elements.append( (int(measure), "repeat bracket" + elem.number) ) repeat_elements.update(instr_repeat_elements) repeat_elements = sorted(list(repeat_elements), key=lambda tup: tup[0]) if verbose: ldebug(f"The repeat elements found in this score are: {str(repeat_elements)}") return repeat_elements