Source code for musicscore.beat

from itertools import accumulate
from typing import List

from math import trunc
from fractions import Fraction

from musicscore.chord import _split_copy, _group_chords, Chord
from musicscore.config import SPLITTABLES, GENERALSPLITTABLES, SPLITTEXCEPTIONS
from musicscore.exceptions import (
    BeatWrongDurationError,
    BeatIsFullError,
    BeatHasNoParentError,
    ChordHasNoQuarterDurationError,
    ChordHasNoMidisError,
    AlreadyFinalizedError,
    BeatNotFullError,
    AddChordError,
    QuarterDurationIsNotWritable,
    BeatUpdateChordTupletsError,
    ChordTypeNotSetError,
)
from musicscore.finalize import FinalizeMixin
from musicscore.musictree import MusicTree
from musicscore.quantize import QuantizeMixin
from musicscore.quarterduration import QuarterDuration, QuarterDurationMixin
from musicscore.tuplet import SimplifiedSextuplets, Tuplet
from musicscore.util import lcm, split_list

__all__ = ["Beat", "beam_chord_group", "get_chord_group_subdivision"]


def _convert_to_quarter_duration_splittables_dictionary(simple_splittalbes):
    output = {}
    for key, value in simple_splittalbes.items():
        output[QuarterDuration(*key)] = {
            QuarterDuration(*k): [QuarterDuration(*qd) for qd in v]
            for k, v in value.items()
        }
    return output


_SPLITTABLE_QUARTER_DURATIONS = _convert_to_quarter_duration_splittables_dictionary(
    SPLITTABLES
)


def _find_nearest_quantized_value(quantized_values, values):
    output = []
    for value in values:
        nearest_quantized = min(
            enumerate(quantized_values), key=lambda x: abs(x[1] - value)
        )[1]
        delta = nearest_quantized - value
        output.append((nearest_quantized, delta))
    return output


def _find_q_delta(quantized_locations, values):
    qs = _find_nearest_quantized_value(quantized_locations, values)
    d = 0
    for q in qs:
        d += abs(q[1])
    return d


def _find_quantized_locations(duration, subdivision):
    output = range(subdivision + 1)
    fr = duration / subdivision
    output = [x * fr for x in output]
    return output


[docs]def get_chord_group_subdivision(chords): qds = [ch.quarter_duration for ch in chords if ch.quarter_duration != 0] if len(qds) == 1: return qds[0].denominator qd_sum = sum(qds) if qd_sum in [3 / 2, 3 / 4, 3, 6]: denominators = list(dict.fromkeys([qd.denominator for qd in qds])) l_c_m = lcm(denominators) if l_c_m not in [1, 2, 4, 8, 16]: raise NotImplementedError else: return l_c_m permitted_sums = [1 / 4, 1 / 2, 1, 2, 4, 8] if qd_sum not in permitted_sums: raise ValueError(f"sum of chords {qd_sum} must be in {permitted_sums}") qds = [qd / qd_sum for qd in qds] denominators = list(dict.fromkeys([qd.denominator for qd in qds])) if len(denominators) > 1: l_c_m = lcm(denominators) if l_c_m not in denominators and l_c_m > 16: return None else: return l_c_m else: return denominators[0]
[docs]def beam_chord_group(chord_group: List["Chord"]) -> None: """ Function for setting beams of a list of chords (chord_group). This function is used to create or update beams inside a beat. Chord types must be set first. """ chord_group = [ch for ch in chord_group if ch.quarter_duration != 0] for ch in chord_group: if ch.type is None: raise ChordTypeNotSetError( "Beaming chord groups not possible if chord types are not set." ) def remove_rests_from_both_ends(chords): is_rest_list = [ch.is_rest for ch in chords] if False not in is_rest_list: return [] if True in is_rest_list: first_non_chord_index = is_rest_list.index(False) output = chords[first_non_chord_index:] is_rest_list = is_rest_list[first_non_chord_index:] if is_rest_list[-1] is True: last_non_chord_index = next( i for i in reversed((range(len(is_rest_list)))) if is_rest_list[i] is True ) output = output[:last_non_chord_index] return output else: return chords chord_group = remove_rests_from_both_ends(chord_group) if not chord_group: return # valid values for XMLBeam are: 'begin', 'continue', 'end', 'forward hook', 'backward hook' def add_beam_to_chord(chord, number, value): if chord.beams is not None and not chord.beams.get(number): chord.set_beam(number, value) def add_last_beam(chord, last_number_of_beams, current_number_of_beams, cont=False): """ cont=True will be for example used to continue one beam (eight beam) in the middle of the beat with 32nds. """ if last_number_of_beams <= current_number_of_beams: if cont: add_beam_to_chord(chord, 1, "continue") for num in range(2, last_number_of_beams + 1): add_beam_to_chord(chord, num, "end") else: for n in range(1, last_number_of_beams + 1): add_beam_to_chord(chord, n, "end") else: if current_number_of_beams != 0: if cont: add_beam_to_chord(chord, 1, "continue") for n in range(2, current_number_of_beams + 1): add_beam_to_chord(chord, n, "end") else: for n in range(1, current_number_of_beams + 1): add_beam_to_chord(chord, n, "end") for n in range(current_number_of_beams + 1, last_number_of_beams + 1): add_beam_to_chord(chord, n, "backward") current_number_of_beams = 0 # adding all necessary beams to all notes save the notes of the last chord. For last chord add_last_beam() will be used. index = 0 while index != len(chord_group) - 1: chord = chord_group[index] next_chord = chord_group[index + 1] types = [] b1, b2 = chord.number_of_beams, next_chord.number_of_beams if chord.is_rest: pass "do nothing" else: if not b1: pass "do nothing" elif next_chord.is_rest: if current_number_of_beams == 0: types.append(("begin", 1, 1)) if b1 > 1: types.append(("forward", 1, b1)) else: types.append(("continue", 1, 1)) if b1 > 1: if b1 <= current_number_of_beams: types.append(("end", 2, b1)) else: types.append(("end", 2, current_number_of_beams)) types.append(("forward", current_number_of_beams + 1, b1)) current_number_of_beams = 1 else: "do something regular" if b1 and not b2: add_last_beam(chord, b1, current_number_of_beams) else: if b2 < b1 <= current_number_of_beams: types.append(("continue", 1, b2)) types.append(("end", b2 + 1, b1)) elif b2 < b1 > current_number_of_beams: if current_number_of_beams == 0: types.append(("begin", 1, b2)) types.append(("forward", b2 + 1, b1)) else: if current_number_of_beams > b2: types.append(("continue", 1, b2)) types.append(("end", b2 + 1, current_number_of_beams)) types.append( ("backward", current_number_of_beams + 1, b1) ) elif current_number_of_beams < b2: types.append(("continue", 1, current_number_of_beams)) types.append(("begin", current_number_of_beams + 1, b2)) types.append(("forward", b2 + 1, b1)) else: types.append(("continue", 1, b2)) types.append(("forward", b2 + 1, b1)) elif b2 == b1 <= current_number_of_beams: types.append(("continue", 1, b1)) elif b2 == b1 > current_number_of_beams: if current_number_of_beams == 0: types.append(("begin", 1, b2)) else: types.append(("continue", 1, current_number_of_beams)) types.append(("begin", current_number_of_beams + 1, b2)) elif b2 > b1 <= current_number_of_beams: types.append(("continue", 1, b1)) elif b2 > b1 > current_number_of_beams: if current_number_of_beams == 0: types.append(("begin", 1, b1)) else: types.append(("continue", 1, current_number_of_beams)) types.append(("begin", current_number_of_beams + 1, b1)) for t in types: for num in range(t[1], t[2] + 1): add_beam_to_chord(chord, num, t[0]) if chord.beams: current_number_of_beams = len( [v for v in chord.beams.values() if v in ["continue", "begin"]] ) if index == len(chord_group) - 2 and b2: add_last_beam(next_chord, b2, current_number_of_beams) index += 1
[docs]class Beat( MusicTree, SimplifiedSextuplets, QuarterDurationMixin, QuantizeMixin, FinalizeMixin ): """ Parent type: :obj:`~musicscore.voice.Voice` Child type: :obj:`~musicscore.chord.Chord` Beat is the direct ancestor of chords. Each :obj:`~musicscore.chord.Chord` is placed with an offset between 0 and beat's quarter duration inside the beat as its child . Quarter duration of a beat's :obj:`~musicscore.chord.Chord` child can exceed its own quarter duration. If a :obj:`~musicscore.chord.Chord` is longer than the quarter duration of beat's parent :obj:`~musicscore.voice.Voice`, a leftover :obj:`~musicscore.chord.Chord` will be added as leftover property to the :obj:`~musicscore.voice.Voice` which will be added to next measure's appropriate voice . Beat manages splitting of each child :obj:`~musicscore.chord.Chord` into appropriate tied :obj:`~musicscore.chord.Chord` s if needed, for example if this chord has a non-writable quarter duration like 5/6. The dots and tuplets are also added here to :obj:`~musicscore.chord.Chord` or directly to their :obj:`~musicscore.note.Note` children. Beaming and quantization are also further important tasks of a beat. """ _PERMITTED_DURATIONS = {4, 2, 1, 0.5} def __init__(self, quarter_duration=1, *args, **kwargs): super().__init__(quarter_duration=quarter_duration, *args, **kwargs) self._filled_quarter_duration = 0 self.leftover_chord = None self._subdivision = None def _add_child(self, child): child._parent = self self._children.append(child) try: self.up.up.up.up.set_current_measure( staff_number=self.up.up.number, voice_number=self.up.number, measure=self.up.up.up, ) except AttributeError: pass def _add_chord(self, chord=None): if chord is None: chord = Chord(midis=60, quarter_duration=self.quarter_duration) return self.add_child(chord) def _change_children_quarter_durations(self, quarter_durations): if len(quarter_durations) != len(self.get_children()): raise ValueError if sum(quarter_durations) != self.quarter_duration: raise ValueError for qd, ch in zip(quarter_durations, self.get_children()): ch._quarter_duration = qd def _check_permitted_duration(self, val): for d in self._PERMITTED_DURATIONS: if val == d: return raise BeatWrongDurationError(f"Beat's quarter duration {val} is not allowed.") def _get_quantized_locations(self, subdivision): return _find_quantized_locations(self.quarter_duration, subdivision) def _get_quantized_quarter_durations(self, quarter_durations): if sum(quarter_durations) != self.quarter_duration: raise ValueError( f"Sum of quarter_durations '{quarter_durations}: {sum(quarter_durations)}' is not equal to beat quarter_duration " f"'{self.quarter_duration}'" ) def _get_positions(): output = [0] for i, qd in enumerate(quarter_durations): output.append(output[i] + qd) return output positions = _get_positions() permitted_divs = self.get_possible_subdivisions()[:] best_div = permitted_divs.pop(0) last_q_delta = _find_q_delta( self._get_quantized_locations(subdivision=best_div), positions ) for div in permitted_divs: current_q_delta = _find_q_delta( self._get_quantized_locations(subdivision=div), positions ) if current_q_delta < last_q_delta: best_div = div last_q_delta = current_q_delta elif (current_q_delta == last_q_delta) and (div < best_div): best_div = div quantized_positions = [ f[0] for f in _find_nearest_quantized_value( self._get_quantized_locations(subdivision=best_div), positions ) ] quantized_durations = [] for i in range(len(quarter_durations)): fr = Fraction( quantized_positions[i + 1] - quantized_positions[i] ).limit_denominator(trunc(best_div / self.quarter_duration)) quantized_durations.append(QuarterDuration(fr)) return quantized_durations @staticmethod def _split_chord(chord, quarter_durations): output = [chord] chord._quarter_duration = quarter_durations[0] for qd in quarter_durations[1:]: copied = _split_copy(chord, qd) output.append(copied) for index, ch in enumerate(output[:-1]): next_ch = output[index + 1] ch.add_tie("start") next_ch.add_tie("stop") for midi in next_ch.midis: midi.accidental.show = False return output def _split_not_writable(self, chord, offset): starting_ties = [] for midi in chord.midis: starting_ties.append(True if midi.is_tied_to_next else False) def _get_quarter_durations(): if _SPLITTABLE_QUARTER_DURATIONS.get( offset ) and _SPLITTABLE_QUARTER_DURATIONS.get(offset).get(chord.quarter_duration): quarter_durations = _SPLITTABLE_QUARTER_DURATIONS.get(offset).get( chord.quarter_duration ) if quarter_durations: return [QuarterDuration(qd) for qd in quarter_durations] if GENERALSPLITTABLES.get(chord.quarter_duration.numerator): quarter_durations = [ QuarterDuration(x, chord.quarter_duration.denominator) for x in GENERALSPLITTABLES.get(chord.quarter_duration.numerator) ] return quarter_durations try: split_qds = ( SPLITTEXCEPTIONS.get(self.quarter_duration.value) .get(self.get_subdivision()) .get(chord.quarter_duration.as_integer_ratio()) ) if split_qds: quarter_durations = [ QuarterDuration(qd[0], qd[1]) for qd in split_qds ] return quarter_durations except AttributeError: return None quarter_durations = _get_quarter_durations() if not quarter_durations: return [chord] output = self._split_chord(chord, quarter_durations) for midi, tied in zip(output[-1].midis, starting_ties): if tied is True: midi.add_tie("start") return output def _update_chord_types(self): for ch in self.get_chords(): if not ch.type: if self.get_subdivision() and not ch.quarter_duration.beat_subdivision: ch.quarter_duration.beat_subdivision = self.get_subdivision() try: ch.type = ch.quarter_duration.get_type() except QuarterDurationIsNotWritable as err: raise QuarterDurationIsNotWritable( f"Chord {ch.get_position_in_tree()} with offset {ch.offset}: {err} Consider setting type, number_of_dots and tuplet properties of the chord manually or splitting it into writable chords." ) def _update_chord_number_of_dots(self): for ch in self.get_chords(): if ch.number_of_dots is None: if self.get_subdivision() and not ch.quarter_duration.beat_subdivision: ch.quarter_duration.beat_subdivision = self.get_subdivision() ch.number_of_dots = ch.quarter_duration.get_number_of_dots() def _update_chord_tuplets(self): non_grace_chords = [ chord for chord in self.get_chords() if chord.quarter_duration != 0 ] # All tuplets are already set manually if None not in {ch.tuplet for ch in non_grace_chords}: return # Check if there are some manually set tuplets. if {ch.tuplet for ch in non_grace_chords} != {None}: raise BeatUpdateChordTupletsError( "Beat cannot manage tuplets automatically if it contains chords with manually set tuplet properties." ) def _update_tuplets(chord_group, actual_notes, quarter_duration=1): if actual_notes <= 16 or actual_notes == 32: if actual_notes not in [1, 2, 4, 8, 16, 32]: # simplified sixtuplets if ( actual_notes == 6 and self.quarter_duration == 1 and self.simplified_sextuplets ): actual_notes = 3 for chord in chord_group: chord.tuplet = Tuplet( actual_notes=actual_notes, quarter_duration=quarter_duration ) if chord == chord_group[0]: chord.tuplet.bracket_type = "start" elif chord == chord_group[-1]: chord.tuplet.bracket_type = "stop" else: pass else: raise NotImplementedError( "tuplets of actual_notes > 16 cannot be implemented." ) actual_notes = get_chord_group_subdivision(non_grace_chords) # simplified sixtuplets if ( actual_notes == 6 and self.quarter_duration == 1 and self.simplified_sextuplets ): qds = [ch.quarter_duration for ch in non_grace_chords] if 1 / 2 in list(accumulate(qds)): grouped_chords = _group_chords(self.get_children(), [1 / 2, 1 / 2]) for g in grouped_chords: actual_notes = get_chord_group_subdivision(g) _update_tuplets(g, actual_notes, 1 / 2) if len(g) == 1: g[0].number_of_dots = 0 return if not actual_notes: if self.quarter_duration == 1: grouped_chords = _group_chords(self.get_children(), [1 / 2, 1 / 2]) if grouped_chords: for g in grouped_chords: actual_notes = get_chord_group_subdivision(g) _update_tuplets(g, actual_notes, 1 / 2) return else: raise NotImplementedError( "Beat cannot be halved. It cannot manage the necessary grouping of chords." ) else: raise NotImplementedError( "Beat with quarter_duration other than one cannot manage more than one group of chords." ) _update_tuplets(non_grace_chords, actual_notes, self.quarter_duration) def _update_chord_beams(self): chords = [ch for ch in self.get_chords() if ch.quarter_duration != 0] if chords: ## update types for ch in chords: if ch.type is None: self._update_chord_types() ## group chords chord_groups = None continue_eighth_beam = False # group inside beat subdivisons = self.get_subdivision() if (subdivisons == 8 and self.quarter_duration == 1) or ( self.simplified_sextuplets and self.quarter_duration == 1 and [ch.quarter_duration * 6 for ch in chords] in [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 2], [2, 1, 1, 1, 1], [2, 1, 1, 2]] ): chord_groups = _group_chords(chords, [1 / 2, 1 / 2]) if chord_groups and 1 not in {len(group) for group in chord_groups}: continue_eighth_beam = True else: chord_groups = None if not chord_groups: chord_groups = [chords] # break beams broken_chord_groups = [] for group in chord_groups: split_indices = [ index for index, chord in enumerate(group) if chord.broken_beam ] for broken_group in split_list(group, split_indices): broken_chord_groups.append(broken_group) # create beams for group in broken_chord_groups: beam_chord_group(group) # continue eighth beam if continue_eighth_beam and 1 not in { len(group) for group in broken_chord_groups }: for index, group in enumerate(broken_chord_groups): if index < len(broken_chord_groups) - 1: group[-1].beams[1] = "continue" if index > 0: group[0].beams[1] = "continue" def _remove_zero_quarter_durations(self): def _get_next_chord(chord): next_chord = chord.next if not next_chord: next_beat = chord.up.next while next_beat and not next_chord: try: next_chord = next_beat.get_children()[0] except IndexError: next_beat = next_beat.next if not next_chord: voice_number = chord.up.up.number staff_number = chord.up.up.up.number if not staff_number: staff_number = 1 next_measure = chord.up.up.up.up.next if next_measure: next_measure_voice = next_measure.get_chord( staff_number=staff_number, voice_number=voice_number ) if next_measure_voice: next_beat = next_measure_voice.get_children()[0] while next_beat and not next_chord: try: next_chord = next_beat.get_children()[0] except IndexError: next_beat = next_beat.next return next_chord def _get_previous_chord(chord): previous_chord = chord.previous if not previous_chord: previous_beat = chord.up.previous while previous_beat and not previous_chord: try: previous_chord = previous_beat.get_children()[-1] except IndexError: previous_beat = previous_beat.previous if not previous_chord: voice_number = chord.up.up.number staff_number = chord.up.up.up.number if not staff_number: staff_number = 1 previous_measure = chord.up.up.up.up.previous if previous_measure: previous_measure_voice = previous_measure.get_chord( staff_number=staff_number, voice_number=voice_number ) if previous_measure_voice: previous_beat = previous_measure_voice.get_children()[-1] while previous_beat and not previous_chord: try: previous_chord = previous_beat.get_children()[-1] except IndexError: previous_beat = previous_beat.previous return previous_chord zeros = [ch for ch in self.get_children() if ch.quarter_duration == 0] for ch in zeros: if ch.all_midis_are_tied_to_next: next_chord = _get_next_chord(ch) # next_chord.add_lyric('I am next') if next_chord: [m.remove_tie("stop") for m in next_chord.midis] next_chord._xml_direction_types = ch._xml_direction_types next_chord._xml_directions = ch._xml_directions next_chord._xml_lyrics = ch._xml_lyrics next_chord._xml_articulations = ch._xml_articulations next_chord._xml_technicals = ch._xml_technicals next_chord._xml_ornaments = ch._xml_ornaments next_chord._xml_dynamics = ch._xml_dynamics next_chord._xml_other_notations = ch._xml_other_notations next_chord._note_attributes = ch._note_attributes ch.up.remove(ch) elif ch.all_midis_are_tied_to_previous: previous_chord = _get_previous_chord(ch) if previous_chord: [m.remove_tie("start") for m in previous_chord.midis] ch.up.remove(ch) else: pass def _split_not_writable_chords(self) -> None: """ This method checks if the quarter duration of all children chords must be split according to :obj:`~musicscore.beat.SPLITTABLES` dictionary. If chord's offset and its quarter duration exist in the dictionary a list of splitting quarter durations can be accessed like this: ``SPLITTABLES[chord.offset[chord.quarter_duration]]`` This dictionary can be manipulated by user during runtime if needed. Be careful with not writable quarter durations which have to be split (for example 5/6 must be split to 3/6, 2/6 or some other writable quarter durations). :obj:`~musicscore.measure.Measure.finalize()` loops over all its beats calls this method. """ for chord in self.get_children()[:]: split = self._split_not_writable(chord, chord.offset) if split: for ch in split: ch._parent = self if chord == self.get_children()[-1]: self._children = self.get_children()[:-1] + split else: index = self.get_children().index(chord) self._children = ( self.get_children()[:index] + split + self.get_children()[index + 1 :] ) @property def is_filled(self) -> bool: """ :return: ``True`` if no children can be added anymore. If ``False`` there is still room for further child or children. :rtype: bool """ if self.filled_quarter_duration == self.quarter_duration: return True else: return False @property def filled_quarter_duration(self): """ :return: How much of beat's quarter duration is already filled. :rtype: QuarterDuration """ return self._filled_quarter_duration @property def number(self) -> int: """ :return: Beat's number inside its parent's :obj:`musicscore.voice.Voice` :rtype: int """ return self.up.get_children().index(self) + 1 @property def offset(self) -> QuarterDuration: """ :return: Offset in Beat's parent :obj:`musicscore.voice.Voice` :rtype: QuarterDuration """ if not self.up: return None elif self.previous is None: return 0 else: return self.previous.offset + self.previous.quarter_duration
[docs] def add_child(self, child: Chord) -> List["Chord"]: """ If child's quarter duration is less than beat's remaining quarter duration: child is added to the beat. If child's quarter duration is greater than beat's remaining quarter duration: :obj:`~musicscore.chord.Chord`'s :obj:`~musicscore.chord.Chord._split_and_add_beatwise` is method called. It is possible to add a chord with a quarter duration exceeding the beat's quarter duration without splitting the chord. For example if the first beat in a 4/4 measure gets a chord with quarter duration 3, the chord will be added to this first beat as a child and the following two beats will be set to filled without having a child themselves and the parent :obj:`~musicscore.voice.Voice` returns the fourth beat if its :obj:`~musicscore.voice.Voice.get_current_beat` is called. If child's quarter duration exceeds the :obj:`~musicscore.voice.Voice`'s remaining quarter duration a leftover :obj:`~musicscore.chord.Chord` will be added to the voice and can be accessed when the next :obj:`~musicscore.measure.Measure` is created. :param child: :obj:`~musicscore.chord.Chord` to be added as child :return: list of split chords """ if self._finalized is True: raise AlreadyFinalizedError(self, "add_child") self._check_child_to_be_added(child) if not self.up: raise BeatHasNoParentError( "A child Chord can only be added to a beat if it has a voice parent." ) if child.quarter_duration is None: raise ChordHasNoQuarterDurationError( "Chord with no quarter_duration cannot be added to Beat." ) if not child.midis: raise ChordHasNoMidisError("Chord with no midis cannot be added to Beat.") if self.is_filled and child.quarter_duration != 0: raise BeatIsFullError() diff = child.quarter_duration - ( self.quarter_duration - self.filled_quarter_duration ) if diff <= 0: self._filled_quarter_duration += child.quarter_duration self._add_child(child) return [child] else: if child.split: remaining_quarter_duration = child.quarter_duration current_beat = self while remaining_quarter_duration and current_beat: if current_beat.quarter_duration < remaining_quarter_duration: current_beat._filled_quarter_duration += ( current_beat.quarter_duration ) remaining_quarter_duration -= current_beat.quarter_duration current_beat = current_beat.next else: current_beat._filled_quarter_duration += ( remaining_quarter_duration ) break self._add_child(child) return [child] else: beats = self.up.get_children()[self.up.get_children().index(self) :] return child._split_and_add_beatwise(beats)
def add_chord(self, *args, **kwargs): raise AddChordError
[docs] def fill_with_rests(self) -> None: """ If :obj:`~musicscore.beat.Beat` is not filled, it will be filled with rest(s) """ if not self.is_filled: self._add_chord( Chord( 0, self.quarter_duration - sum([ch.quarter_duration for ch in self.get_chords()]), ) )
[docs] def finalize(self): """ finalize can only be called once. - It calls finalize method of all :obj:`~musicscore.chord.Chord` children. - Following updates are triggered: _update_note_tuplets, _update_chord_beams, quantize_quarter_durations (if get_quantized is True), _split_not_writable_chords """ if self._finalized: raise AlreadyFinalizedError(self) if self.is_filled is False: self.fill_with_rests() if self.get_children(): self._update_chord_types() self._update_chord_number_of_dots() self._update_chord_tuplets() self._update_chord_beams() for chord in self.get_children(): chord.finalize() self._finalized = True
[docs] def get_subdivision(self): if self._subdivision is None: if not self.is_filled: raise BeatNotFullError() self._subdivision = get_chord_group_subdivision(self.get_chords()) return self._subdivision
[docs] def set_subdivision(self, val): self._subdivision = val
[docs] def quantize_quarter_durations(self): """ When called the positioning of children will be quantized according to :obj:`~musicscore.quantize.QuantizeMixin.get_possible_subdivisions()` This method is called by :obj:`~musicscore.measure.Measure` """ if self.get_possible_subdivisions() and self.get_children(): if ( get_chord_group_subdivision(self.get_children()) in self.get_possible_subdivisions() ): pass else: quarter_durations = [ chord.quarter_duration for chord in self.get_children() ] if len([d for d in quarter_durations if d != 0]) > 1: self._change_children_quarter_durations( self._get_quantized_quarter_durations(quarter_durations) ) self._remove_zero_quarter_durations()