Source code for mutwo.abjad_converters.events.quantization

"""Module to quantize free :class:`Consecution` to notation based abjad :class:`Container`"""

import abc
import typing
import warnings

try:
    import quicktions as fractions  # type: ignore
except ImportError:
    import fractions  # type: ignore

import abjad  # type: ignore
from abjadext import nauert  # type: ignore
import ranges  # type: ignore

from mutwo import core_converters
from mutwo import core_events
from mutwo import core_parameters
from mutwo import core_utilities

__all__ = (
    "ConsecutionToQuantizedAbjadContainer",
    "NauertConsecutionToQuantizedAbjadContainer",
    "NauertConsecutionToDurationLineBasedQuantizedAbjadContainer",
    "LeafMakerConsecutionToQuantizedAbjadContainer",
    "LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer",
)


class NoTimeSignatureError(Exception):
    pass


# XXX: In the future `default_tempo_envelope` should be set to `None` and
# `mutwo` should, by default, use `event_to_tempo_envelope`. Then
# `default_tempo_envelope` should be removed completely.
[docs]class ConsecutionToQuantizedAbjadContainer(core_converters.abc.Converter): """Quantize :class:`~mutwo.core_events.Consecution` objects. :param default_time_signature_sequence: Set time signatures to divide the quantized abjad data in desired bar sizes. If the converted :class:`~mutwo.core_events.Consecution` is longer than the sum of all passed time signatures, the last time signature will be repeated for the remaining bars. :type default_time_signature_sequence: typing.Sequence[abjad.TimeSignature] :param event_to_time_signature_tuple: Function which extracts a `tuple[abjad.TimeSignature, ...]` from a :class:`mutwo.core_events.abc.Event`. If set to `None` `mutwo` falls back to `default_time_signature_sequence`. Default to `None`. """ def __init__( self, default_time_signature_sequence: typing.Sequence[abjad.TimeSignature] = ( abjad.TimeSignature((4, 4)), ), event_to_time_signature_tuple: typing.Optional[ typing.Callable[ [core_events.abc.Event], typing.Optional[tuple[abjad.TimeSignature, ...]], ] ] = None, ): default_time_signature_sequence_count = len(default_time_signature_sequence) if default_time_signature_sequence_count == 0: raise NoTimeSignatureError( "Found empty sequence for argument " "'default_time_signature_sequence_count'. " "Specify at least one time signature!" ) default_time_signature_tuple = tuple(default_time_signature_sequence) self._default_time_signature_tuple = default_time_signature_tuple self._event_to_time_signature_tuple = event_to_time_signature_tuple def _get_time_signature_tuple( self, event: core_events.abc.Event ) -> tuple[abjad.TimeSignature, ...]: if self._event_to_time_signature_tuple: if time_signature_tuple := self._event_to_time_signature_tuple(event): return time_signature_tuple return self._default_time_signature_tuple # ###################################################################### # # public methods for interaction with the user # # ###################################################################### #
[docs] @abc.abstractmethod def convert( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...]]: ...
[docs]class NauertConsecutionToQuantizedAbjadContainer( ConsecutionToQuantizedAbjadContainer ): """Quantize :class:`~mutwo.core_events.Consecution` objects via :mod:`abjadext.nauert`. :param time_signature_sequence: Set time signatures to divide the quantized abjad data in desired bar sizes. If the converted :class:`~mutwo.core_events.Consecution` is longer than the sum of all passed time signatures, the last time signature will be repeated for the remaining bars. :param duration_unit: This defines the `duration_unit` of the passed :class:`~mutwo.core_events.Consecution` (how the :attr:`~mutwo.core_events.abc.Event.duration` attribute will be interpreted). Can either be 'beats' (default) or 'miliseconds'. WARNING: 'miliseconds' isn't working properly yet! :param attack_point_optimizer: Optionally the user can pass a :class:`nauert.AttackPointOptimizer` object. Attack point optimizer help to split events and tie them for better looking notation. The default attack point optimizer is :class:`nauert.MeasurewiseAttackPointOptimizer` which splits events to better represent metrical structures within bars. If no optimizer is desired this argument can be set to ``None``. Unlike :class:`LeafMakerConsecutionToQuantizedAbjadContainer` this converter supports nested tuplets and ties across tuplets. But this converter is much slower than the :class:`LeafMakerConsecutionToQuantizedAbjadContainer`. Because the converter depends on the abjad extension `nauert` its quality is dependent on the inner mechanism of the used package. Because the quantization made by the `nauert` package can be somewhat indeterministic a lot of tweaking may be necessary for complex musical structures. """ # TODO(add proper miliseconds conversion: you will have to add the tempo_envelope # when building the QEventSequence. Furthermore you should auto write down the # metronome marks when initialising from miliseconds?) def __init__( self, default_time_signature_sequence: typing.Sequence[abjad.TimeSignature] = ( abjad.TimeSignature((4, 4)), ), duration_unit: str = "beats", # for future: typing.Literal["beats", "miliseconds"] attack_point_optimizer: typing.Optional[ nauert.AttackPointOptimizer ] = nauert.MeasurewiseAttackPointOptimizer(), search_tree: typing.Optional[nauert.SearchTree] = None, **kwargs, ): if duration_unit == "miliseconds": # warning for not well implemented miliseconds conversion warnings.warn( "The current implementation can't apply tempo changes for duration unit" " 'miliseconds' yet! Furthermore to quantize via duration_unit" " 'miliseconds' isn't well tested yet and may return unexpected" " results." ) default_time_signature_tuple = tuple(default_time_signature_sequence) # nauert will raise an error if there is only one time signature if len(default_time_signature_tuple) == 1: default_time_signature_tuple += default_time_signature_tuple super().__init__(default_time_signature_tuple, **kwargs) self._duration_unit = duration_unit self._attack_point_optimizer = attack_point_optimizer self._search_tree = search_tree # ###################################################################### # # static methods # # ###################################################################### # @staticmethod def _get_respective_q_event_from_abjad_leaf( abjad_leaf: typing.Union[abjad.Rest, abjad.Note] ) -> typing.Optional[nauert.QEvent]: # TODO(improve ugly, heuristic, unreliable code) try: return abjad.get.indicators(abjad_leaf)[0]["q_events"][0] except TypeError: return None except KeyError: return None except IndexError: return None @staticmethod def _process_abjad_leaf( indices: list[int], abjad_leaf: abjad.Leaf, related_abjad_leaves_per_chronon: list[list[tuple[int, ...]]], q_event_sequence: nauert.QEventSequence, has_tie: bool, index_of_previous_q_event: int, ) -> tuple[bool, int]: q_event = NauertConsecutionToQuantizedAbjadContainer._get_respective_q_event_from_abjad_leaf( abjad_leaf ) if q_event and type(q_event) != nauert.TerminalQEvent: nth_q_event = q_event_sequence.sequence.index(q_event) related_abjad_leaves_per_chronon[nth_q_event].append(tuple(indices)) index_of_previous_q_event = nth_q_event elif has_tie: related_abjad_leaves_per_chronon[index_of_previous_q_event].append( tuple(indices) ) # skip leaves without any links # else: # related_abjad_leaves_per_chronon.append([tuple(indices)]) has_tie = abjad.get.has_indicator(abjad_leaf, abjad.Tie) return has_tie, index_of_previous_q_event @staticmethod def _process_tuplet( indices: list[int], tuplet: abjad.Tuplet, related_abjad_leaves_per_chronon: list[list[tuple[int, ...]]], q_event_sequence: nauert.QEventSequence, has_tie: bool, index_of_previous_q_event: int, ) -> tuple[bool, int]: for ( nth_abjad_leaf_or_tuplet, abjad_leaf_or_tuplet, ) in enumerate(tuplet): ( has_tie, index_of_previous_q_event, ) = NauertConsecutionToQuantizedAbjadContainer._process_abjad_leaf_or_tuplet( indices + [nth_abjad_leaf_or_tuplet], abjad_leaf_or_tuplet, related_abjad_leaves_per_chronon, q_event_sequence, has_tie, index_of_previous_q_event, ) return has_tie, index_of_previous_q_event @staticmethod def _process_abjad_leaf_or_tuplet( index_list: list[int], abjad_leaf_or_tuplet: typing.Union[abjad.Tuplet, abjad.Leaf], related_abjad_leaves_per_chronon: list[list[tuple[int, ...]]], q_event_sequence: nauert.QEventSequence, has_tie: bool, index_of_previous_q_event: int, ) -> tuple[bool, int]: if isinstance(abjad_leaf_or_tuplet, abjad.Tuplet): return NauertConsecutionToQuantizedAbjadContainer._process_tuplet( index_list, abjad_leaf_or_tuplet, related_abjad_leaves_per_chronon, q_event_sequence, has_tie, index_of_previous_q_event, ) else: return NauertConsecutionToQuantizedAbjadContainer._process_abjad_leaf( index_list, abjad_leaf_or_tuplet, related_abjad_leaves_per_chronon, q_event_sequence, has_tie, index_of_previous_q_event, ) @staticmethod def _make_related_abjad_leaves_per_chronon( consecution: core_events.Consecution, q_event_sequence: nauert.QEventSequence, quanitisized_abjad_leaf_voice: abjad.Voice, ) -> tuple[tuple[tuple[int, ...], ...], ...,]: has_tie = False index_of_previous_q_event: int = 0 related_abjad_leaves_per_chronon: list[list[tuple[int, ...]]] = [ [] for _ in consecution ] for nth_bar, bar in enumerate(quanitisized_abjad_leaf_voice): for nth_abjad_leaf_or_tuplet, abjad_leaf_or_tuplet in enumerate(bar): ( has_tie, index_of_previous_q_event, ) = NauertConsecutionToQuantizedAbjadContainer._process_abjad_leaf_or_tuplet( [nth_bar, nth_abjad_leaf_or_tuplet], abjad_leaf_or_tuplet, related_abjad_leaves_per_chronon, q_event_sequence, has_tie, index_of_previous_q_event, ) return tuple( tuple(tuple(item) for item in pair) for pair in related_abjad_leaves_per_chronon ) @staticmethod def _make_q_schema( time_signature_tuple: tuple[abjad.TimeSignature, ...], search_tree: typing.Optional[nauert.SearchTree], ) -> nauert.QSchema: formated_time_signature_list = [] for time_signature in time_signature_tuple: formated_time_signature_list.append({"time_signature": time_signature}) keyword_arguments = { "use_full_measure": True, "tempo": abjad.MetronomeMark((1, 4), 60), } if search_tree: keyword_arguments.update({"search_tree": search_tree}) return nauert.MeasurewiseQSchema( *formated_time_signature_list, **keyword_arguments ) # ###################################################################### # # private methods # # ###################################################################### # def _get_q_schema(self, event: core_events.abc.Event) -> nauert.MeasurewiseQSchema: return NauertConsecutionToQuantizedAbjadContainer._make_q_schema( self._get_time_signature_tuple(event), self._search_tree ) def _consecution_to_q_event_sequence( self, consecution: core_events.Consecution ) -> nauert.QEventSequence: duration_list = list(consecution.get_parameter("duration")) for nth_chronon, chronon in enumerate(consecution): if chronon.is_rest: duration_list[nth_chronon] = ( core_parameters.DirectDuration(0) - duration_list[nth_chronon] ) if self._duration_unit == "beats": return nauert.QEventSequence.from_tempo_scaled_durations( duration_list, tempo=abjad.MetronomeMark((1, 4), 60) ) elif self._duration_unit == "miliseconds": return nauert.QEventSequence.from_millisecond_durations(duration_list) else: message = ( "Unknown duration unit '{}'. Use duration unit 'beats' or" " 'miliseconds'.".format(self._duration_unit) ) raise NotImplementedError(message) def _q_event_sequence_to_quanitisized_abjad_leaf_voice( self, q_event_sequence: nauert.QEventSequence, q_schema: nauert.MeasurewiseQSchema, ) -> abjad.Voice: quantizer = nauert.Quantizer() return quantizer( q_event_sequence, q_schema=q_schema, attach_tempos=True if self._duration_unit == "miliseconds" else False, attack_point_optimizer=self._attack_point_optimizer, ) # ###################################################################### # # public methods for interaction with the user # # ###################################################################### #
[docs] def convert( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]: q_event_sequence = self._consecution_to_q_event_sequence( consecution_to_convert ) q_schema = self._get_q_schema(consecution_to_convert) quanitisized_abjad_leaf_voice = ( self._q_event_sequence_to_quanitisized_abjad_leaf_voice( q_event_sequence, q_schema ) ) related_abjad_leaves_per_chronon = NauertConsecutionToQuantizedAbjadContainer._make_related_abjad_leaves_per_chronon( consecution_to_convert, q_event_sequence, quanitisized_abjad_leaf_voice ) return ( quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon, )
[docs]class LeafMakerConsecutionToQuantizedAbjadContainer( ConsecutionToQuantizedAbjadContainer ): """Quantize :class:`~mutwo.core_events.Consecution` object via :mod:`abjad.LeafMaker`. :param time_signature_sequence: Set time signatures to divide the quantized abjad data in desired bar sizes. If the converted :class:`~mutwo.core_events.Consecution` is longer than the sum of all passed time signatures, the last time signature will be repeated for the remaining bars. This method is significantly faster than the :class:`NauertConsecutionToQuantizedAbjadContainer`. But it also has several known limitations: 1. :class:`LeafMakerConsecutionToQuantizedAbjadContainer` doesn't support nested tuplets. 2. :class:`LeafMakerConsecutionToQuantizedAbjadContainer` doesn't support ties across tuplets with different prolation (or across tuplets and not-tuplet notation). If ties are desired the user has to build them manually before passing the :class:`~mutwo.core_events.Consecution` to the converter. """ _maximum_dot_count = 1 def __init__( self, *args, do_rewrite_meter: bool = True, add_beams: bool = True, **kwargs ): self._leaf_maker = abjad.LeafMaker( forbidden_note_duration=abjad.Duration(8, 1), forbidden_rest_duration=abjad.Duration(8, 1), ) super().__init__(*args, **kwargs) self._do_rewrite_meter = do_rewrite_meter self._add_beams = add_beams # ###################################################################### # # static private methods # # ###################################################################### # @staticmethod def _find_offset_inventory(meter: abjad.Meter) -> tuple[abjad.Offset, ...]: for nth_offset_inventory, offset_inventory in enumerate( depthwise_offset_inventory := meter.depthwise_offset_inventory ): difference = offset_inventory[1] - offset_inventory[0] if difference == fractions.Fraction(1, 4): return offset_inventory elif difference <= fractions.Fraction(1, 4): return depthwise_offset_inventory[nth_offset_inventory - 1] return offset_inventory @staticmethod def _add_explicit_beams( bar: abjad.Container, meter: abjad.Meter, global_offset: abjad.Offset ) -> None: offset_inventory = ( LeafMakerConsecutionToQuantizedAbjadContainer._find_offset_inventory( meter ) ) leaf_offset_list = [] # don't attach beams on tuplets relevant_bar_items = filter( lambda leaf_or_tuplet: isinstance(leaf_or_tuplet, abjad.Leaf) and leaf_or_tuplet.written_duration < fractions.Fraction(1, 4), bar, ) leaf_selection = abjad.select.leaves(relevant_bar_items) for leaf in leaf_selection: offset = abjad.get.timespan(leaf).start_offset - global_offset leaf_offset_list.append(offset) beam_range_list = [] for start, end in zip(offset_inventory, offset_inventory[1:]): area = ranges.Range(start, end) offset_tuple = tuple( filter(lambda offset: offset in area, leaf_offset_list) ) n_elements = len(offset_tuple) is_start_in_leaves = start in offset_tuple # make new beam range if is_start_in_leaves and n_elements > 1: new_beam_range = [ leaf_offset_list.index(offset_tuple[0]), leaf_offset_list.index(offset_tuple[-1]), ] beam_range_list.append(new_beam_range) for beam_range in beam_range_list: start, stop = beam_range abjad.attach(abjad.StartBeam(), leaf_selection[start]) abjad.attach(abjad.StopBeam(), leaf_selection[stop]) global_offset += offset_inventory[-1] return global_offset @staticmethod def _find_tuplet_indices(bar: abjad.Container) -> tuple[int, ...]: tuplet_index_list = [] for index, leaf_or_tuplet in enumerate(bar): if isinstance(leaf_or_tuplet, abjad.Tuplet): tuplet_index_list.append(index) return tuple(tuplet_index_list) @staticmethod def _group_tuplet_indices(tuplet_index_tuple: tuple[int, ...]) -> list[list[int]]: """Put adjacent tuplet indices into groups.""" grouped_tuplet_index_list = [[]] last_tuplet_index = None for tuplet_index in tuplet_index_tuple: if last_tuplet_index: difference = tuplet_index - last_tuplet_index if difference == 1: grouped_tuplet_index_list[-1].append(tuplet_index) else: grouped_tuplet_index_list.append([tuplet_index]) else: grouped_tuplet_index_list[-1].append(tuplet_index) last_tuplet_index = tuplet_index return grouped_tuplet_index_list @staticmethod def _concatenate_adjacent_tuplets_for_one_group( bar: abjad.Container, group: list[int] ): implied_prolation_list = [bar[index].implied_prolation for index in group] common_prolation_group_list = [[implied_prolation_list[0], [group[0]]]] for index, prolation in zip(group[1:], implied_prolation_list[1:]): if prolation == common_prolation_group_list[-1][0]: common_prolation_group_list[-1][1].append(index) else: common_prolation_group_list.append([prolation, [index]]) tuplet_list = [] for prolation, tuplet_index_list in common_prolation_group_list: tuplet = abjad.Tuplet(prolation) for tuplet_index in tuplet_index_list: for component in bar[tuplet_index]: tuplet.append(abjad.mutate.copy(component)) tuplet_list.append(tuplet) bar[group[0] : group[-1] + 1] = tuplet_list # ###################################################################### # # private methods # # ###################################################################### # def _make_note_tuple( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Leaf, ...]: pitch_list = [ None if event.is_rest else "c" for event in consecution_to_convert ] # It has to be a list! Otherwise abjad will raise an exception. duration_list = list( map( lambda duration: abjad.Duration(duration), consecution_to_convert.get_parameter("duration"), ) ) note_tuple = tuple(self._leaf_maker(pitch_list, duration_list)) return note_tuple def _concatenate_adjacent_tuplets_for_one_bar(self, bar: abjad.Container): tuplet_index_tuple = ( LeafMakerConsecutionToQuantizedAbjadContainer._find_tuplet_indices(bar) ) if tuplet_index_tuple: grouped_tuplet_index_list_list = ( LeafMakerConsecutionToQuantizedAbjadContainer._group_tuplet_indices( tuplet_index_tuple ) ) for tuplet_index_list in reversed(grouped_tuplet_index_list_list): if len(tuplet_index_list) > 1: LeafMakerConsecutionToQuantizedAbjadContainer._concatenate_adjacent_tuplets_for_one_group( bar, tuplet_index_list ) def _concatenate_adjacent_tuplets(self, voice: abjad.Voice) -> abjad.Voice: for bar in voice: self._concatenate_adjacent_tuplets_for_one_bar(bar) def _rewrite_meter(self, voice: abjad.Voice): time_signature_iter = iter(self._time_signature_tuple) last_time_signature = self._time_signature_tuple[-1] # rewrite by meter global_offset = abjad.Offset((0, 1)) previous_time_signature = None for bar in voice: try: time_signature = next(time_signature_iter) except StopIteration: time_signature = last_time_signature if time_signature != previous_time_signature: abjad.attach(time_signature, abjad.get.leaf(bar, 0)) meter = abjad.Meter(time_signature) abjad.Meter.rewrite_meter( bar[:], time_signature, maximum_dot_count=self._maximum_dot_count ) if self._add_beams: global_offset = self._add_explicit_beams(bar, meter, global_offset) previous_time_signature = time_signature last_bar = bar difference = time_signature.duration - abjad.get.duration(last_bar) if difference: last_bar.extend(self._leaf_maker([None], [difference])) abjad.Meter.rewrite_meter( last_bar[:], time_signature, maximum_dot_count=self._maximum_dot_count ) def _make_voice( self, consecution_to_convert: core_events.Consecution ) -> abjad.Voice: # first build notes note_tuple = self._make_note_tuple(consecution_to_convert) # split notes by time signatures notes_split_by_time_signature_sequence = abjad.mutate.split( note_tuple, [time_signature.duration for time_signature in self._time_signature_tuple], cyclic=True, ) bar_list = [] for selection in notes_split_by_time_signature_sequence: try: bar = abjad.Container(selection, simultaneous=False) except Exception: bar = abjad.Container(abjad.mutate.copy(selection), simultaneous=False) bar_list.append(bar) voice = abjad.Voice(bar_list) if self._do_rewrite_meter: self._rewrite_meter(voice) self._concatenate_adjacent_tuplets(voice) return voice def _get_data_for_leaf( self, index_tuple: tuple[int, ...], leaf: abjad.Leaf ) -> tuple[tuple[int, ...], bool, bool]: has_tie = abjad.get.indicator(leaf, abjad.Tie()) is_rest = ( isinstance(leaf, abjad.Rest) or isinstance(leaf, abjad.MultimeasureRest) or isinstance(leaf, abjad.Skip) ) return index_tuple, has_tie, is_rest def _get_data_for_tuplet_or_leaf( self, index_tuple: tuple[int, ...], leaf_or_tuplet: typing.Union[abjad.Leaf, abjad.Tuplet], ) -> tuple[tuple[tuple[int, ...], bool], ...]: if isinstance(leaf_or_tuplet, abjad.Leaf): return (self._get_data_for_leaf(index_tuple, leaf_or_tuplet),) else: data_per_leaf_or_tuplet_list = [] for nth_leaf_or_tuplet_of_tuplet, sub_leaf_or_tuplet in enumerate( leaf_or_tuplet ): data_per_leaf_or_tuplet_list.extend( self._get_data_for_tuplet_or_leaf( index_tuple + (nth_leaf_or_tuplet_of_tuplet,), sub_leaf_or_tuplet, ) ) return tuple(data_per_leaf_or_tuplet_list) def _make_related_abjad_leaves_per_chronon( self, voice: abjad.Voice ) -> tuple[tuple[tuple[int, ...], ...], ...]: data_per_tuplet_or_leaf_list = [] for nth_bar, bar in enumerate(voice): for nth_leaf_or_tuplet, leaf_or_tuplet in enumerate(bar): data_per_tuplet_or_leaf_list.extend( self._get_data_for_tuplet_or_leaf( (nth_bar, nth_leaf_or_tuplet), leaf_or_tuplet ) ) related_abjad_leaves_per_chronon = [] related_abjad_leaves = [] was_previous_note_rest = None has_previous_tie = None for index_tuple, has_tie, is_rest in data_per_tuplet_or_leaf_list: if has_previous_tie or all((was_previous_note_rest, is_rest)): related_abjad_leaves.append(index_tuple) else: if related_abjad_leaves: related_abjad_leaves_per_chronon.append( tuple(related_abjad_leaves) ) related_abjad_leaves = [index_tuple] has_previous_tie = has_tie was_previous_note_rest = is_rest if related_abjad_leaves: related_abjad_leaves_per_chronon.append(tuple(related_abjad_leaves)) return tuple(related_abjad_leaves_per_chronon)
[docs] def convert( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]: # FIXME(bad style) self._time_signature_tuple = self._get_time_signature_tuple( consecution_to_convert ) voice = self._make_voice(consecution_to_convert) related_abjad_leaves_per_chronon = ( self._make_related_abjad_leaves_per_chronon(voice) ) return voice, related_abjad_leaves_per_chronon
class _DurationLineBasedQuantizedAbjadContainerMixin(object): """Mixin for duration-line based quantization. :param duration_line_minimum_length: The minimum length of a duration line. :type duration_line_minimum_length: int :param duration_line_thickness: The thickness of a duration line. :type duration_line_thickness: int This converter differs from its parent class through the usage of duration lines for indicating rhythm instead of using flags, beams, dots and note head colors. **Note**: Don't forget to add the 'Duration_line_engraver' to the resulting abjad Voice, otherwise Lilypond won't be able to render the desired output. **Example:** >>> import abjad >>> from mutwo import abjad_converters >>> from mutwo import core_events >>> converter = abjad_converters.ConsecutionToAbjadVoiceConverter( >>> abjad_converters.LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer( >>> ) >>> ) >>> consecution_to_convert = core_events.Consecution( >>> [ >>> music_events.NoteLike("c", 0.125), >>> music_events.NoteLike("d", 1), >>> music_events.NoteLike([], 0.125), >>> music_events.NoteLike("e", 0.16666), >>> music_events.NoteLike("e", 0.08333333333333333) >>> ] >>> ) >>> converted_consecution = converter.convert(consecution_to_convert) >>> converted_consecution.consists_commands.append("Duration_line_engraver") """ def __init__( self, *, duration_line_minimum_length: int = 6, duration_line_thickness: int = 3, ): self._duration_line_minimum_length = duration_line_minimum_length self._duration_line_thickness = duration_line_thickness @classmethod def _set_docs(cls, parent: type): doc_base = parent.__doc__.split("\n\n") doc_mixin = _DurationLineBasedQuantizedAbjadContainerMixin.__doc__.split("\n\n") cls.__doc__ = "\n\n".join( [doc_base[0], doc_base[1] + "\n" + doc_mixin[1]] + doc_mixin[2:] ) def _prepare_first_element(self, first_element: abjad.Leaf): # set duration line properties abjad.attach( abjad.LilyPondLiteral( "\\override Staff.DurationLine.minimum-length = {}".format( self._duration_line_minimum_length ) ), first_element, ) abjad.attach( abjad.LilyPondLiteral( "\\override Staff.DurationLine.thickness = {}".format( self._duration_line_thickness ) ), first_element, ) def _adjust_quantisized_abjad_leaves( self, quanitisized_abjad_leaf_voice: abjad.Container, related_abjad_leaves_per_chronon: tuple[tuple[tuple[int, ...], ...], ...], ): is_first = True for abjad_leaves_indices in related_abjad_leaves_per_chronon: if abjad_leaves_indices: first_element = core_utilities.get_nested_item_from_index_sequence( abjad_leaves_indices[0], quanitisized_abjad_leaf_voice ) if is_first: self._prepare_first_element(first_element) is_first = False is_active = bool(abjad.get.pitches(first_element)) if is_active: if len(abjad_leaves_indices) > 1: abjad.detach(abjad.Tie(), first_element) abjad.attach( abjad.LilyPondLiteral("\\-", site="after"), first_element ) for indices in abjad_leaves_indices[1:]: element = core_utilities.get_nested_item_from_index_sequence( indices, quanitisized_abjad_leaf_voice ) core_utilities.set_nested_item_from_index_sequence( indices, quanitisized_abjad_leaf_voice, abjad.Skip(element.written_duration), )
[docs]class NauertConsecutionToDurationLineBasedQuantizedAbjadContainer( NauertConsecutionToQuantizedAbjadContainer, _DurationLineBasedQuantizedAbjadContainerMixin, ): def __init__( self, *args, duration_line_minimum_length: int = 6, duration_line_thickness: int = 3, **kwargs, ): super().__init__(*args, **kwargs) _DurationLineBasedQuantizedAbjadContainerMixin.__init__( self, duration_line_minimum_length=duration_line_minimum_length, duration_line_thickness=duration_line_thickness, )
[docs] def convert( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]: ( quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon, ) = super().convert(consecution_to_convert) self._adjust_quantisized_abjad_leaves( quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon ) return quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon
[docs]class LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer( LeafMakerConsecutionToQuantizedAbjadContainer, _DurationLineBasedQuantizedAbjadContainerMixin, ): def __init__( self, *args, duration_line_minimum_length: int = 6, duration_line_thickness: int = 3, **kwargs, ): super().__init__(*args, **kwargs) _DurationLineBasedQuantizedAbjadContainerMixin.__init__( self, duration_line_thickness=duration_line_thickness, duration_line_minimum_length=duration_line_minimum_length, )
[docs] def convert( self, consecution_to_convert: core_events.Consecution ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]: ( quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon, ) = super().convert(consecution_to_convert) self._adjust_quantisized_abjad_leaves( quanitisized_abjad_leaf_voice, related_abjad_leaves_per_chronon ) # only assign first item to abjad leaves post_processed_releated_abjad_leaves_per_chronon = [] for related_abjad_leaves in related_abjad_leaves_per_chronon: post_processed_releated_abjad_leaves_per_chronon.append( (related_abjad_leaves[0],) ) return ( quanitisized_abjad_leaf_voice, post_processed_releated_abjad_leaves_per_chronon, )
NauertConsecutionToDurationLineBasedQuantizedAbjadContainer._set_docs( NauertConsecutionToQuantizedAbjadContainer ) LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer._set_docs( LeafMakerConsecutionToQuantizedAbjadContainer )