Source code for mutwo.abjad_converters.events.building

"""Module to build complex multi-level abjad based scores from mutwo events."""

import abc
import inspect
import itertools
import typing

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

import abjad  # type: ignore

from mutwo import abjad_converters
from mutwo import abjad_parameters
from mutwo import core_converters
from mutwo import core_events
from mutwo import core_parameters
from mutwo import core_utilities
from mutwo import music_converters
from mutwo import music_parameters

from ..parameters import MutwoPitchToAbjadPitch
from ..parameters import MutwoVolumeToAbjadAttachmentDynamic
from ..parameters import TempoEnvelopeToAbjadAttachmentTempo
from ..parameters import ComplexTempoEnvelopeToAbjadAttachmentTempo
from ..parameters import MutwoLyricToAbjadString

from .quantization import ConsecutionToQuantizedAbjadContainer
from .quantization import NauertConsecutionToQuantizedAbjadContainer

from .quantization import (
    NauertConsecutionToDurationLineBasedQuantizedAbjadContainer,
)
from .quantization import (
    LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer,
)


__all__ = (
    "ComplexEventToAbjadContainer",
    "ConsecutionToAbjadVoice",
    "NestedComplexEventToAbjadContainer",
    "NestedComplexEventToComplexEventToAbjadContainers",
    "CycleBasedNestedComplexEventToComplexEventToAbjadContainers",
    "TagBasedNestedComplexEventToComplexEventToAbjadContainers",
)


[docs]class ComplexEventToAbjadContainer(core_converters.abc.Converter): def __init__( self, abjad_container_class: typing.Type[abjad.Container], lilypond_type_of_abjad_container: str, complex_event_to_abjad_container_name: typing.Callable[ [core_events.abc.ComplexEvent], str ], pre_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ], post_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ], ): self._abjad_container_class = abjad_container_class self._lilypond_type_of_abjad_container = lilypond_type_of_abjad_container self._complex_event_to_abjad_container_name = ( complex_event_to_abjad_container_name ) self._pre_process_abjad_container_routine_sequence = ( pre_process_abjad_container_routine_sequence ) self._post_process_abjad_container_routine_sequence = ( post_process_abjad_container_routine_sequence ) def _make_empty_abjad_container( self, complex_event_to_converter: core_events.abc.ComplexEvent ) -> abjad.Container: abjad_container_name = core_utilities.call_function_except_attribute_error( self._complex_event_to_abjad_container_name, complex_event_to_converter, None, ) kwargs = {} argument_tuple = tuple( inspect.signature(self._abjad_container_class).parameters.keys() ) if "simultaneous" in argument_tuple: kwargs.update( { "simultaneous": isinstance( complex_event_to_converter, core_events.Concurrence ) } ) if abjad_container_name and "name" in argument_tuple: kwargs.update({"name": abjad_container_name}) if self._lilypond_type_of_abjad_container and "lilypond_type" in argument_tuple: kwargs.update({"lilypond_type": self._lilypond_type_of_abjad_container}) return self._abjad_container_class([], **kwargs) def _pre_process_abjad_container( self, complex_event_to_convert: core_events.abc.ComplexEvent, abjad_container_to_pre_process: abjad.Container, ): for ( pre_process_abjad_container_routine ) in self._pre_process_abjad_container_routine_sequence: pre_process_abjad_container_routine( complex_event_to_convert, abjad_container_to_pre_process ) def _post_process_abjad_container( self, complex_event_to_convert: core_events.abc.ComplexEvent, abjad_container_to_post_process: abjad.Container, ): for ( post_process_abjad_container_routine ) in self._post_process_abjad_container_routine_sequence: post_process_abjad_container_routine( complex_event_to_convert, abjad_container_to_post_process ) @abc.abstractmethod def _fill_abjad_container( self, abjad_container_to_fill: abjad.Container, complex_event_to_convert: core_events.abc.ComplexEvent, ): raise NotImplementedError
[docs] def convert( self, complex_event_to_convert: core_events.abc.ComplexEvent ) -> abjad.Container: abjad_container = self._make_empty_abjad_container(complex_event_to_convert) self._pre_process_abjad_container(complex_event_to_convert, abjad_container) self._fill_abjad_container(abjad_container, complex_event_to_convert) self._post_process_abjad_container(complex_event_to_convert, abjad_container) return abjad_container
[docs]class ConsecutionToAbjadVoice(ComplexEventToAbjadContainer): """Convert :class:`~mutwo.core_events.Consecution` to :class:`abjad.Voice`. :param consecution_to_quantized_abjad_container: Class which defines how the Mutwo data will be quantized. See :class:`ConsecutionToQuantizedAbjadContainer` for more information. :type consecution_to_quantized_abjad_container: ConsecutionToQuantizedAbjadContainer, optional :param default_tempo_envelope: Fallback value in case `event_to_tempo_envelope` is set to `None` or returns `None`. This is the default for now, but likely to be removed in the future. If possible, better use `event_to_tempo_envelope`. :type default_tempo_envelope: core_events.TempoEnvelope :param chronon_to_pitch_list: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a tuple that contains pitch objects (objects that inherit from :class:`mutwo.music_parameters.abc.Pitch`). By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.pitch_list` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their pitch property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no pitch can be extracted), mutwo will assume an event without any pitches. :type chronon_to_pitch_list: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Pitch], optional :param chronon_to_volume: Function to extract the volume from a :class:`mutwo.core_events.SimpleEvent` in the purpose of generating dynamic indicators. The function should return an object that inherits from :class:`mutwo.music_parameters.abc.Volume`. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.volume` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their volume property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no volume can be extracted), mutwo will set :attr:`pitch_list` to an empty list and set volume to 0. :type chronon_to_volume: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Volume], optional :param chronon_to_grace_note_consecution: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`~mutwo.core_events.Consecution` object filled with :class:`~mutwo.core_events.SimpleEvent`. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.grace_note_consecution` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their `grace_note_consecution` property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no grace_note_consecution can be extracted), mutwo will use an empty :class:`~mutwo.core_events.Consecution`. :type chronon_to_grace_note_consecution: typing.Callable[[core_events.SimpleEvent], core_events.Consecution[core_events.SimpleEvent]], optional :param chronon_to_after_grace_note_consecution: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`~mutwo.core_events.Consecution` object filled with :class:`~mutwo.core_events.SimpleEvent`. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.after_grace_note_consecution` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their `after_grace_note_consecution` property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no after_grace_note_consecution can be extracted), mutwo will use an empty :class:`~mutwo.core_events.Consecution`. :type chronon_to_after_grace_note_consecution: typing.Callable[[core_events.SimpleEvent], core_events.Consecution[core_events.SimpleEvent]], optional :param chronon_to_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.playing_indicators.PlayingIndicatorCollection` object. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.playing_indicator_collection` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their playing_indicators property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no playing indicator collection can be extracted), mutwo will build a playing indicator collection from :const:`~mutwo.music_events.configurations.DEFAULT_PLAYING_INDICATORS_COLLECTION_CLASS`. :type chronon_to_playing_indicator_collection: typing.Callable[[core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection,], optional :param chronon_to_notation_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.notation_indicators.NotationIndicatorCollection` object. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.notation_indicators` (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than ``NoteLike`` with a different name for their playing_indicators property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no notation indicator collection can be extracted), mutwo will build a notation indicator collection from :const:`~mutwo.music_events.configurations.DEFAULT_NOTATION_INDICATORS_COLLECTION_CLASS` :type chronon_to_notation_indicator_collection: typing.Callable[[core_events.SimpleEvent], music_parameters.NotationIndicatorCollection,], optional :param chronon_to_lyric: Function to extract the lyric from a :class:`mutwo.core_events.SimpleEvent` in the purpose of generating lyrics. The function should return an object that inherits from :class:`mutwo.music_parameters.abc.Lyric`. By default it asks the Event for its :attr:`~mutwo.music_events.NoteLike.lyric` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). When using different Event classes than :class:`~mutwo.music_events.NoteLike` with a different name for their lyric property, this argument should be overridden. If the function call raises an :obj:`AttributeError` (e.g. if no lyric can be extracted), mutwo will set :attr:`lyric` to an empty text. :type chronon_to_lyric: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Lyric], optional :param is_chronon_rest: Function to detect if the the inspected :class:`mutwo.core_events.SimpleEvent` is a Rest. By default Mutwo simply checks if 'pitch_list' contain any objects. If not, the Event will be interpreted as a rest. :type is_chronon_rest: typing.Callable[[core_events.SimpleEvent], bool], optional :param mutwo_pitch_to_abjad_pitch: Class which defines how to convert :class:`mutwo.music_parameters.abc.Pitch` objects to :class:`abjad.Pitch` objects. See :class:`MutwoPitchToAbjadPitch` for more information. :type mutwo_pitch_to_abjad_pitch: MutwoPitchToAbjadPitch, optional :param mutwo_volume_to_abjad_attachment_dynamic: Class which defines how to convert :class:`mutwo.music_parameters.abc.Volume` objects to :class:`mutwo.abjad_parameters.Dynamic` objects. See :class:`MutwoVolumeToAbjadAttachmentDynamic` for more information. :type mutwo_volume_to_abjad_attachment_dynamic: MutwoVolumeToAbjadAttachmentDynamic, optional :param tempo_envelope_to_abjad_attachment_tempo: Class which defines how to convert tempo envelopes to :class:`mutwo.abjad_parameters.Tempo` objects. See :class:`TempoEnvelopeToAbjadAttachmentTempo` for more information. :type tempo_envelope_to_abjad_attachment_tempo: TempoEnvelopeToAbjadAttachmentTempo, optional :param mutwo_lyric_to_abjad_string: Callable which defines how to convert :class:`mutwo.music_parameters.abc.Lyric` to a string. Consult :class:`mutwo.abjad_converters.MutwoLyricToAbjadString` for more information. :type mutwo_lyric_to_abjad_string: MutwoLyricToAbjadString :param event_to_tempo_envelope: A function which extracts a :class:`mutwo.core_events.TempoEnvelope` from a :class:`mutwo.core_events.abc.Event`. If set to `None` or if the function returns `None` mutwo falls back to `default_tempo_envelope`. Default to ``None``, but this will likely change in the future. :type event_to_tempo_envelope: typing.Optional[typing.Callable[[core_events.abc.Event], typing.Optional[core_events.TempoEnvelope]]] :param abjad_attachment_class_sequence: A tuple which contains all available abjad attachment classes which shall be used by the converter. :type abjad_attachment_class_sequence: typing.Sequence[abjad_parameters.abc.AbjadAttachment], optional :param write_multimeasure_rests: Set to ``True`` if the converter should replace rests that last a complete bar with multimeasure rests (rests with uppercase "R" in Lilypond). Default to ``True``. :type write_multimeasure_rests: bool :param duration_line_engraver: If `consecution_to_quantized_abjad_container` is any duration line based converter, the converter adds :class:`mutwo.abjad_converters.AddDurationLineEngraver` to `post_process_abjad_container_routine_sequence`. Default to ``True``. :type duration_line_engraver: bool :param prepare_for_duration_line_based_notation: If `consecution_to_quantized_abjad_container` is any duration line based converter, the converter adds :class:`mutwo.abjad_converters.PrepareForDurationLineBasedNotation` to `post_process_abjad_container_routine_sequence`. Default to ``True``. :type prepare_for_duration_line_based_notation: bool """ ExtractedData = tuple[ list[music_parameters.abc.Pitch], music_parameters.abc.Volume, core_events.Consecution[core_events.SimpleEvent], core_events.Consecution[core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, music_parameters.NotationIndicatorCollection, music_parameters.abc.Lyric, ] ExtractedDataPerSimpleEvent = tuple[ExtractedData, ...] def __init__( self, consecution_to_quantized_abjad_container: ConsecutionToQuantizedAbjadContainer = NauertConsecutionToQuantizedAbjadContainer(), default_tempo_envelope: core_events.TempoEnvelope = core_events.TempoEnvelope( ( (0, core_parameters.DirectTempoPoint(120)), (0, core_parameters.DirectTempoPoint(120)), ) ), chronon_to_pitch_list: typing.Callable[ [core_events.SimpleEvent], list[music_parameters.abc.Pitch] ] = music_converters.SimpleEventToPitchList(), chronon_to_volume: typing.Callable[ [core_events.SimpleEvent], music_parameters.abc.Volume ] = music_converters.SimpleEventToVolume(), chronon_to_grace_note_consecution: typing.Callable[ [core_events.SimpleEvent], core_events.Consecution[core_events.SimpleEvent], ] = music_converters.SimpleEventToGraceNoteConsecution(), chronon_to_after_grace_note_consecution: typing.Callable[ [core_events.SimpleEvent], core_events.Consecution[core_events.SimpleEvent], ] = music_converters.SimpleEventToAfterGraceNoteConsecution(), chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), chronon_to_notation_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.NotationIndicatorCollection, ] = music_converters.SimpleEventToNotationIndicatorCollection(), chronon_to_lyric: typing.Callable[ [core_events.SimpleEvent], music_parameters.abc.Lyric, ] = music_converters.SimpleEventToLyric(), is_chronon_rest: typing.Optional[ typing.Callable[[core_events.SimpleEvent], bool] ] = None, mutwo_pitch_to_abjad_pitch: MutwoPitchToAbjadPitch = MutwoPitchToAbjadPitch(), mutwo_volume_to_abjad_attachment_dynamic: typing.Optional[ MutwoVolumeToAbjadAttachmentDynamic ] = MutwoVolumeToAbjadAttachmentDynamic(), tempo_envelope_to_abjad_attachment_tempo: typing.Optional[ TempoEnvelopeToAbjadAttachmentTempo ] = ComplexTempoEnvelopeToAbjadAttachmentTempo(), mutwo_lyric_to_abjad_string: MutwoLyricToAbjadString = MutwoLyricToAbjadString(), event_to_tempo_envelope: typing.Optional[ typing.Callable[ [core_events.abc.Event], typing.Optional[core_events.TempoEnvelope] ] ] = None, abjad_attachment_class_sequence: typing.Sequence[ typing.Type[abjad_parameters.abc.AbjadAttachment] ] = None, write_multimeasure_rests: bool = True, abjad_container_class: typing.Type[abjad.Container] = abjad.Voice, lilypond_type_of_abjad_container: str = "Voice", complex_event_to_abjad_container_name: typing.Callable[ [core_events.abc.ComplexEvent], typing.Optional[str] ] = lambda _: None, pre_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ] = tuple([]), post_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ] = tuple([]), duration_line_engraver: bool = True, prepare_for_duration_line_based_notation: bool = True, ): self._with_duration_line = isinstance( consecution_to_quantized_abjad_container, ( NauertConsecutionToDurationLineBasedQuantizedAbjadContainer, LeafMakerConsecutionToDurationLineBasedQuantizedAbjadContainer, ), ) # special treatment for duration line based quantizer if self._with_duration_line: post_process_abjad_container_routine_sequence = list( post_process_abjad_container_routine_sequence ) if duration_line_engraver: post_process_abjad_container_routine_sequence.append( abjad_converters.AddDurationLineEngraver() ) if prepare_for_duration_line_based_notation: post_process_abjad_container_routine_sequence.append( abjad_converters.PrepareForDurationLineBasedNotation() ) post_process_abjad_container_routine_sequence = tuple( post_process_abjad_container_routine_sequence ) super().__init__( abjad_container_class, lilypond_type_of_abjad_container, complex_event_to_abjad_container_name, pre_process_abjad_container_routine_sequence, post_process_abjad_container_routine_sequence, ) if abjad_attachment_class_sequence is None: abjad_attachment_class_sequence = ( abjad_converters.configurations.DEFAULT_ABJAD_ATTACHMENT_CLASS_TUPLE ) else: abjad_attachment_class_sequence = tuple(abjad_attachment_class_sequence) if is_chronon_rest is None: def is_chronon_rest(chronon: core_events.SimpleEvent) -> bool: pitch_list = core_utilities.call_function_except_attribute_error( chronon_to_pitch_list, chronon, [] ) return not bool(pitch_list) self._abjad_attachment_class_sequence = abjad_attachment_class_sequence self._available_attachment_tuple = tuple( abjad_attachment_class.get_class_name() for abjad_attachment_class in self._abjad_attachment_class_sequence ) self._consecution_to_quantized_abjad_container = ( consecution_to_quantized_abjad_container ) self._chronon_to_pitch_list = chronon_to_pitch_list self._chronon_to_volume = chronon_to_volume self._chronon_to_grace_note_consecution = ( chronon_to_grace_note_consecution ) self._chronon_to_after_grace_note_consecution = ( chronon_to_after_grace_note_consecution ) self._chronon_to_playing_indicator_collection = ( chronon_to_playing_indicator_collection ) self._chronon_to_notation_indicator_collection = ( chronon_to_notation_indicator_collection ) self._chronon_to_lyric = chronon_to_lyric self._chronon_to_function_tuple = ( self._chronon_to_grace_note_consecution, self._chronon_to_after_grace_note_consecution, self._chronon_to_playing_indicator_collection, self._chronon_to_notation_indicator_collection, self._chronon_to_lyric, ) self._is_chronon_rest = is_chronon_rest self._mutwo_pitch_to_abjad_pitch = mutwo_pitch_to_abjad_pitch self._mutwo_volume_to_abjad_attachment_dynamic = ( mutwo_volume_to_abjad_attachment_dynamic ) self._tempo_envelope_to_abjad_attachment_tempo = ( tempo_envelope_to_abjad_attachment_tempo ) self._default_tempo_envelope = default_tempo_envelope self._event_to_tempo_envelope = event_to_tempo_envelope self._mutwo_lyric_to_abjad_string = mutwo_lyric_to_abjad_string self._write_multimeasure_rests = write_multimeasure_rests # ###################################################################### # # static methods # # ###################################################################### # @staticmethod def _detect_abjad_event_type(pitch_list: list[music_parameters.abc.Pitch]) -> type: pitch_count = len(pitch_list) if pitch_count == 0: abjad_event_type = abjad.Rest elif pitch_count == 1: abjad_event_type = abjad.Note else: abjad_event_type = abjad.Chord return abjad_event_type @staticmethod def _find_absolute_times_of_abjad_leaves( abjad_voice: abjad.Voice, ) -> tuple[fractions.Fraction, ...]: absolute_time_per_leaf_list: list[fractions.Fraction] = [] for leaf in abjad.select.leaves(abjad_voice): start, _ = abjad.get.timespan(leaf).offsets absolute_time_per_leaf_list.append( fractions.Fraction(start.numerator, start.denominator) ) return tuple(absolute_time_per_leaf_list) @staticmethod def _replace_rests_with_full_measure_rests(abjad_voice: abjad.Voice) -> None: def ok(indicator_sequence) -> bool: for indicator in indicator_sequence: if isinstance( indicator, abjad_converters.constants.INEFFECTIVE_INDICATOR_FOR_MULTIMEASURE_REST_TUPLE, ): return False return True for bar in abjad_voice: # We can only replace rests with multi measure rests if certain # requirments apply: # First ensure we only have rests in this bar. # Then we need to ensure that none of those rests have an # indicator: if they would have an indicator it would be # lost, because MultiMeasureRest can't print plenty of # attachments (for instance fermata). if all((isinstance(item, abjad.Rest) for item in bar)) and all( [ok(abjad.get.indicators(item)) for item in bar] ): duration = sum((item.written_duration for item in bar)) numerator, denominator = duration.numerator, duration.denominator abjad.mutate.replace( bar[0], abjad.MultimeasureRest( abjad.Duration(1, denominator), multiplier=numerator ), wrappers=True, ) del bar[1:] # ###################################################################### # # private methods # # ###################################################################### # def _get_tempo_envelope( self, event: core_events.abc.Event ) -> core_events.TempoEnvelope: if self._event_to_tempo_envelope: if tempo_envelope := self._event_to_tempo_envelope(event): return tempo_envelope return self._default_tempo_envelope def _indicator_collection_to_abjad_parameters( self, indicator_collection: music_parameters.abc.IndicatorCollection, ) -> dict[str, abjad_parameters.abc.AbjadAttachment]: attachment_dict = {} for abjad_attachment_class in self._abjad_attachment_class_sequence: abjad_attachment = abjad_attachment_class.from_indicator_collection( indicator_collection, is_chronon_rest=self._is_chronon_rest, mutwo_pitch_to_abjad_pitch=self._mutwo_pitch_to_abjad_pitch, mutwo_volume_to_abjad_attachment_dynamic=self._mutwo_volume_to_abjad_attachment_dynamic, mutwo_lyric_to_abjad_string=self._mutwo_lyric_to_abjad_string, with_duration_line=self._with_duration_line, ) if abjad_attachment: attachment_dict.update( {abjad_attachment_class.get_class_name(): abjad_attachment} ) return attachment_dict def _grace_note_consecution_to_abjad_attachment( self, grace_note_consecution_or_after_grace_note_consecution: core_events.Consecution[ core_events.SimpleEvent ], is_before: bool, ) -> dict[str, abjad_parameters.abc.AbjadAttachment]: if not grace_note_consecution_or_after_grace_note_consecution: return {} converter = _GraceNotesToAbjadVoiceConverter( is_before, self._chronon_to_pitch_list, self._chronon_to_volume, self._chronon_to_playing_indicator_collection, self._chronon_to_notation_indicator_collection, self._is_chronon_rest, self._mutwo_pitch_to_abjad_pitch, ) grace_note_consecution_container = converter.convert( grace_note_consecution_or_after_grace_note_consecution ) if is_before: name = "grace_note_consecution" abjad_attachment_class = abjad_parameters.GraceNoteConsecution else: name = "after_grace_note_consecution" abjad_attachment_class = abjad_parameters.AfterGraceNoteConsecution return {name: abjad_attachment_class(grace_note_consecution_container)} def _volume_to_abjad_attachment( self, volume: music_parameters.abc.Volume ) -> dict[str, abjad_parameters.abc.AbjadAttachment]: if self._mutwo_volume_to_abjad_attachment_dynamic: abjad_attachment_dynamic = ( self._mutwo_volume_to_abjad_attachment_dynamic.convert(volume) ) if abjad_attachment_dynamic: return {"dynamic": abjad_attachment_dynamic} return {} def _get_tempo_attachment_tuple_for_quantized_abjad_leaves( self, abjad_voice: abjad.Voice, tempo_attachment_tuple: tuple[ tuple[core_parameters.abc.Duration, abjad_parameters.Tempo], ... ], ) -> tuple[ tuple[ int, typing.Union[ abjad_parameters.Tempo, abjad_parameters.DynamicChangeIndicationStop ], ], ..., ]: absolute_time_per_leaf = ( ConsecutionToAbjadVoice._find_absolute_times_of_abjad_leaves( abjad_voice ) ) assert absolute_time_per_leaf == tuple(sorted(absolute_time_per_leaf)) leaf_index_to_tempo_attachment_pairs_list: list[ tuple[ int, typing.Union[ abjad_parameters.Tempo, abjad_parameters.DynamicChangeIndicationStop, ], ] ] = [] for absolute_time, tempo_attachment in tempo_attachment_tuple: closest_leaf = core_utilities.find_closest_index( absolute_time.duration, absolute_time_per_leaf ) # special case: # check for stop dynamic change indication # (has to applied to the previous leaf for # better looking results) if tempo_attachment.stop_dynamic_change_indicaton: leaf_index_to_tempo_attachment_pairs_list.append( (closest_leaf - 1, abjad_parameters.DynamicChangeIndicationStop()) ) leaf_index_to_tempo_attachment_pairs_list.append( (closest_leaf, tempo_attachment) ) return tuple(leaf_index_to_tempo_attachment_pairs_list) def _get_abjad_parameters_for_quantized_abjad_leaves( self, extracted_data_per_chronon: ExtractedDataPerSimpleEvent, ) -> tuple[tuple[typing.Optional[abjad_parameters.abc.AbjadAttachment], ...], ...]: abjad_parameters_per_type_per_event: dict[ str, list[typing.Optional[abjad_parameters.abc.AbjadAttachment]] ] = { attachment_name: [None for _ in extracted_data_per_chronon] for attachment_name in self._available_attachment_tuple } for nth_event, extracted_data in enumerate(extracted_data_per_chronon): ( _, volume, grace_note_consecution, after_grace_note_consecution, playing_indicators, notation_indicators, *_, ) = extracted_data abjad_parameters_for_nth_event = self._volume_to_abjad_attachment(volume) abjad_parameters_for_nth_event.update( self._grace_note_consecution_to_abjad_attachment( grace_note_consecution, True ) ) abjad_parameters_for_nth_event.update( self._grace_note_consecution_to_abjad_attachment( after_grace_note_consecution, False ) ) abjad_parameters_for_nth_event.update( self._indicator_collection_to_abjad_parameters(playing_indicators) ) abjad_parameters_for_nth_event.update( self._indicator_collection_to_abjad_parameters(notation_indicators) ) for attachment_name, attachment in abjad_parameters_for_nth_event.items(): abjad_parameters_per_type_per_event[attachment_name][ nth_event ] = attachment return tuple( tuple(abjad_parameters) for abjad_parameters in abjad_parameters_per_type_per_event.values() ) def _apply_tempos_on_quantized_abjad_leaves( self, quanitisized_abjad_leaf_voice: abjad.Voice, tempo_envelope: core_events.TempoEnvelope, ): tempo_attachment_tuple = None if self._tempo_envelope_to_abjad_attachment_tempo: tempo_attachment_tuple = ( self._tempo_envelope_to_abjad_attachment_tempo.convert(tempo_envelope) ) if tempo_attachment_tuple: leaves = abjad.select.leaves(quanitisized_abjad_leaf_voice) tempo_attachment_data = ( self._get_tempo_attachment_tuple_for_quantized_abjad_leaves( quanitisized_abjad_leaf_voice, tempo_attachment_tuple ) ) for nth_event, tempo_attachment in tempo_attachment_data: try: tempo_attachment.process_leaf_tuple((leaves[nth_event],), None) except abjad.exceptions.PersistentIndicatorError: pass def _apply_abjad_parameters_on_quantized_abjad_leaves( self, quanitisized_abjad_leaf_voice: abjad.Voice, related_abjad_leaf_index_tuple_tuple_per_chronon: tuple[ tuple[tuple[int, ...], ...], ... ], abjad_parameters_per_type_per_event_tuple: tuple[ tuple[typing.Optional[abjad_parameters.abc.AbjadAttachment], ...], ... ], ) -> None: index_tuple_to_remove_list: list[tuple[int, ...]] = [] # All indicators which don't replace leaf-by-leaf can # potentially break indicators which do replace leaf-by-leaf: # Because the second category expects leaf-only input, but the # first category may create outputs which break with this rule. # To fix this we ensure that all leaf-by-leaf indicators are applied # before more complex converters start. def filter_key(abjad_parameters_per_type): abjad_parameters_per_type = tuple(filter(bool, abjad_parameters_per_type)) if abjad_parameters_per_type: return int(abjad_parameters_per_type[0].replace_leaf_by_leaf is False) else: return 0 abjad_parameters_per_type_per_event_tuple = sorted( abjad_parameters_per_type_per_event_tuple, key=filter_key, ) for abjad_parameters_per_type in abjad_parameters_per_type_per_event_tuple: previous_attachment = None for related_abjad_leaf_index_tuple_tuple, attachment in zip( related_abjad_leaf_index_tuple_tuple_per_chronon, abjad_parameters_per_type, ): if attachment and attachment.is_active: index_tuple_to_remove_list.extend( self._apply_abjad_attachment( attachment, previous_attachment, quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple, ) ) previous_attachment = attachment for index_tuple in reversed(index_tuple_to_remove_list): core_utilities.del_nested_item_from_index_sequence( index_tuple, quanitisized_abjad_leaf_voice ) def _apply_abjad_attachment( self, attachment: abjad_parameters.abc.AbjadAttachment, previous_attachment: typing.Optional[abjad_parameters.abc.AbjadAttachment], quanitisized_abjad_leaf_voice: abjad.Voice, related_abjad_leaf_index_tuple_tuple, ) -> tuple[tuple[int, ...]]: abjad_leaf_tuple = tuple( core_utilities.get_nested_item_from_index_sequence( index_tuple, quanitisized_abjad_leaf_voice, ) for index_tuple in related_abjad_leaf_index_tuple_tuple ) processed_abjad_leaf_tuple = attachment.process_leaf_tuple( abjad_leaf_tuple, previous_attachment ) if attachment.replace_leaf_by_leaf: assert len(processed_abjad_leaf_tuple) == len( related_abjad_leaf_index_tuple_tuple ), f"Attachment '{attachment}' returned bad abjad_leaf_tuple!" else: processed_abjad_leaf_tuple = (processed_abjad_leaf_tuple,) + ( (None,) * (len(related_abjad_leaf_index_tuple_tuple) - 1) ) index_tuple_to_remove_list = [] for processed_abjad_leaf, index_tuple in zip( processed_abjad_leaf_tuple, related_abjad_leaf_index_tuple_tuple ): if processed_abjad_leaf is None: # We can't immediately call __delitem__, because # this would confuse all other indices! index_tuple_to_remove_list.append(index_tuple) else: core_utilities.set_nested_item_from_index_sequence( index_tuple, quanitisized_abjad_leaf_voice, processed_abjad_leaf, ) return tuple(index_tuple_to_remove_list) def _extract_pitch_list_and_volume_from_chronon( self, chronon: core_events.SimpleEvent ) -> tuple[list[music_parameters.abc.Pitch], music_parameters.abc.Volume]: pitch_list = self._chronon_to_pitch_list(chronon) # TODO(Add option: no dynamic indicator if there aren't any pitches) if pitch_list: volume = self._chronon_to_volume(chronon) if not volume.amplitude: pitch_list = [] else: volume = music_parameters.DirectVolume(0) return pitch_list, volume def _extract_data_from_chronon( self, chronon: core_events.SimpleEvent ) -> ExtractedData: # Special case for pitch_list and volume: # if pitch_list is empty, there is also no volume. If volume is empty # there is also no pitch_list. extracted_data = list( self._extract_pitch_list_and_volume_from_chronon(chronon) ) for function in self._chronon_to_function_tuple: extracted_data.append(function(chronon)) # type: ignore return tuple(extracted_data) # type: ignore def _apply_pitch_list_on_quantized_abjad_leaf( self, quanitisized_abjad_leaf_voice: abjad.Voice, abjad_pitch_list: list[abjad.Pitch], related_abjad_leaf_index_tuple_tuple: tuple[tuple[int, ...], ...], ): if len(abjad_pitch_list) == 1: leaf_class = abjad.Note else: leaf_class = abjad.Chord for related_abjad_leaf_index_tuple in related_abjad_leaf_index_tuple_tuple: abjad_leaf = core_utilities.get_nested_item_from_index_sequence( related_abjad_leaf_index_tuple, quanitisized_abjad_leaf_voice ) if leaf_class == abjad.Note: # skip don't have note heads if hasattr(abjad_leaf, "note_head"): abjad_leaf.note_head._written_pitch = abjad_pitch_list[0] else: new_abjad_leaf = leaf_class( [abjad.NamedPitch() for _ in abjad_pitch_list], abjad_leaf.written_duration, ) for indicator in abjad.get.indicators(abjad_leaf): if type(indicator) != dict: abjad.attach(indicator, new_abjad_leaf) for abjad_pitch, note_head in zip( abjad_pitch_list, new_abjad_leaf.note_heads ): note_head._written_pitch = abjad_pitch core_utilities.set_nested_item_from_index_sequence( related_abjad_leaf_index_tuple, quanitisized_abjad_leaf_voice, new_abjad_leaf, ) def _apply_pitches_on_quantized_abjad_leaves( self, quanitisized_abjad_leaf_voice: abjad.Voice, related_abjad_leaf_index_tuple_tuple_per_chronon: tuple[ tuple[tuple[int, ...], ...], ... ], extracted_data_per_chronon: ExtractedDataPerSimpleEvent, is_chronon_rest_per_chronon: tuple[bool, ...], ): for ( is_chronon_rest, extracted_data, related_abjad_leaf_index_tuple_tuple, ) in zip( is_chronon_rest_per_chronon, extracted_data_per_chronon, related_abjad_leaf_index_tuple_tuple_per_chronon, ): if not is_chronon_rest: pitch_list = extracted_data[0] abjad_pitch_list = [ self._mutwo_pitch_to_abjad_pitch.convert(pitch) for pitch in pitch_list ] self._apply_pitch_list_on_quantized_abjad_leaf( quanitisized_abjad_leaf_voice, abjad_pitch_list, related_abjad_leaf_index_tuple_tuple, ) def _get_lyric_content( self, extracted_data_per_chronon: ExtractedDataPerSimpleEvent, is_chronon_rest_per_chronon: tuple[bool, ...], ) -> str: lyric_content_list = [] for extracted_data, is_chronon_rest in zip( extracted_data_per_chronon, is_chronon_rest_per_chronon ): if not is_chronon_rest: lyric = extracted_data[6] abjad_string = self._mutwo_lyric_to_abjad_string(lyric) lyric_content_list.append(abjad_string) return " ".join(lyric_content_list) def _apply_lyrics_on_voice( self, voice_to_apply_lyrics_to: abjad.Voice, lyric_content: str ): # We only add the lyrics in case it isn't empty reduced_lyric = tuple(set(filter(bool, lyric_content.split(" ")))) test_tuple = ( # "" -> this doesn't need to be added bool(lyric_content), # " " -> this doesn't need to be added not lyric_content.isspace(), # "_ _ _ _" -> this doesn't need to be added len(reduced_lyric) != 1 or reduced_lyric[0] != "_", ) if all(test_tuple): abjad.attach( abjad.LilyPondLiteral( "\\addlyrics { " + lyric_content + " }", site="absolute_after", ), voice_to_apply_lyrics_to, ) def _quantize_consecution( self, consecution_to_convert: core_events.Consecution[ core_events.SimpleEvent ], is_chronon_rest_per_chronon: tuple[bool, ...], ) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]: is_chronon_rest_per_chronon_iterator = iter( is_chronon_rest_per_chronon ) ( quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple_per_chronon, ) = self._consecution_to_quantized_abjad_container.convert( consecution_to_convert.set_parameter( # type: ignore "is_rest", lambda _: next(is_chronon_rest_per_chronon_iterator), set_unassigned_parameter=True, mutate=False, # type: ignore ) ) return ( quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple_per_chronon, ) def _fill_abjad_container( self, abjad_container_to_fill: abjad.Voice, consecution_to_convert: core_events.Consecution[ core_events.SimpleEvent ], ): # tie rests before processing the event! consecution_to_convert = consecution_to_convert.tie_by( lambda event0, event1: self._is_chronon_rest(event0) and self._is_chronon_rest(event1), event_type_to_examine=core_events.SimpleEvent, mutate=False, # type: ignore ) # first, extract data from chronons and find rests extracted_data_per_chronon = tuple( self._extract_data_from_chronon(chronon) for chronon in consecution_to_convert ) is_chronon_rest_per_chronon = tuple( self._is_chronon_rest(chronon) for chronon in consecution_to_convert ) # second, quantize the consecution ( quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple_per_chronon, ) = self._quantize_consecution( consecution_to_convert, is_chronon_rest_per_chronon ) # third, apply pitches on Abjad voice self._apply_pitches_on_quantized_abjad_leaves( quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple_per_chronon, extracted_data_per_chronon, is_chronon_rest_per_chronon, ) # fourth, apply dynamics, tempos and playing_indicators on abjad voice abjad_parameters_per_type_per_event = ( self._get_abjad_parameters_for_quantized_abjad_leaves( extracted_data_per_chronon ) ) tempo_envelope = self._get_tempo_envelope(consecution_to_convert) self._apply_tempos_on_quantized_abjad_leaves( quanitisized_abjad_leaf_voice, tempo_envelope ) self._apply_abjad_parameters_on_quantized_abjad_leaves( quanitisized_abjad_leaf_voice, related_abjad_leaf_index_tuple_tuple_per_chronon, abjad_parameters_per_type_per_event, ) # fifth, replace rests lasting one bar with full measure rests if self._write_multimeasure_rests: ConsecutionToAbjadVoice._replace_rests_with_full_measure_rests( quanitisized_abjad_leaf_voice ) # move leaves from 'quanitisized_abjad_leaf_voice' object to target container abjad.mutate.swap(quanitisized_abjad_leaf_voice, abjad_container_to_fill) # finally: apply lyrics on abjad voice lyric_content = self._get_lyric_content( extracted_data_per_chronon, is_chronon_rest_per_chronon ) self._apply_lyrics_on_voice(abjad_container_to_fill, lyric_content) # ###################################################################### # # public methods for interaction with the user # # ###################################################################### #
[docs] def convert( self, consecution_to_convert: core_events.Consecution[ core_events.SimpleEvent ], ) -> abjad.Voice: """Convert passed :class:`~mutwo.core_events.Consecution`. :param consecution_to_convert: The :class:`~mutwo.core_events.Consecution` which shall be converted to the :class:`abjad.Voice` object. :type consecution_to_convert: mutwo.core_events.Consecution **Example:** >>> import abjad >>> from mutwo.events import basic, music >>> from mutwo.converters.frontends import abjad as mutwo_abjad >>> mutwo_melody = basic.Consecution( >>> [ >>> music.NoteLike(pitch, duration) >>> for pitch, duration in zip("c a g e".split(" "), (1, 1 / 6, 1 / 6, 1 / 6)) >>> ] >>> ) >>> converter = mutwo_abjad.ConsecutionToAbjadVoice() >>> abjad_melody = converter.convert(mutwo_melody) >>> abjad.lilypond(abjad_melody) \\new Voice { { \\tempo 4=120 %%% \\time 4/4 %%% c'1 \\mf } { \\times 2/3 { a'4 g'4 e'4 } r2 } } """ return super().convert(consecution_to_convert)
class _GraceNotesToAbjadVoiceConverter(ConsecutionToAbjadVoice): class GraceNotesToQuantizedAbjadContainerConverter(core_converters.abc.Converter): def convert( self, consecution_to_convert: core_events.Consecution ) -> abjad.Container: container = abjad.Container([], simultaneous=False) indices = [] for nth_event, event in enumerate(consecution_to_convert): leaf = abjad.Note("c", event.duration) container.append(leaf) indices.append(((nth_event,),)) return container, tuple(indices) def __init__( self, is_before: bool, chronon_to_pitch_list: typing.Callable[ [core_events.SimpleEvent], list[music_parameters.abc.Pitch] ], chronon_to_volume: typing.Callable[ [core_events.SimpleEvent], music_parameters.abc.Volume ], chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ], chronon_to_notation_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.NotationIndicatorCollection, ], is_chronon_rest: typing.Callable[[core_events.SimpleEvent], bool], mutwo_pitch_to_abjad_pitch: MutwoPitchToAbjadPitch, ): if is_before: abjad_container_class = abjad.BeforeGraceContainer else: abjad_container_class = abjad.AfterGraceContainer super().__init__( consecution_to_quantized_abjad_container=self.GraceNotesToQuantizedAbjadContainerConverter(), chronon_to_pitch_list=chronon_to_pitch_list, chronon_to_volume=chronon_to_volume, chronon_to_playing_indicator_collection=chronon_to_playing_indicator_collection, chronon_to_notation_indicator_collection=chronon_to_notation_indicator_collection, is_chronon_rest=is_chronon_rest, mutwo_pitch_to_abjad_pitch=mutwo_pitch_to_abjad_pitch, mutwo_volume_to_abjad_attachment_dynamic=None, tempo_envelope_to_abjad_attachment_tempo=None, chronon_to_grace_note_consecution=lambda _: core_events.Consecution( [] ), chronon_to_after_grace_note_consecution=lambda _: core_events.Consecution( [] ), write_multimeasure_rests=False, abjad_container_class=abjad_container_class, lilypond_type_of_abjad_container=None, ) def _grace_note_consecution_to_abjad_attachment( self, grace_note_consecution_or_after_grace_note_consecution: core_events.Consecution[ core_events.SimpleEvent ], is_before: bool, ) -> dict[str, abjad_parameters.abc.AbjadAttachment]: return {} def _get_tempo_attachment_tuple_for_quantized_abjad_leaves( self, abjad_voice: abjad.Voice, ) -> tuple[ tuple[ int, typing.Union[ abjad_parameters.Tempo, abjad_parameters.DynamicChangeIndicationStop ], ], ..., ]: return tuple([])
[docs]class NestedComplexEventToComplexEventToAbjadContainers(core_converters.abc.Converter):
[docs] @abc.abstractmethod def convert( self, nested_complex_event_to_convert: core_events.abc.ComplexEvent ) -> tuple[ComplexEventToAbjadContainer, ...]: raise NotImplementedError
[docs]class CycleBasedNestedComplexEventToComplexEventToAbjadContainers( NestedComplexEventToComplexEventToAbjadContainers ): def __init__( self, complex_event_to_abjad_container_converter_sequence: typing.Sequence[ ComplexEventToAbjadContainer ], ): self._complex_event_to_abjad_container_converters = ( complex_event_to_abjad_container_converter_sequence )
[docs] def convert( self, nested_complex_event_to_convert: core_events.abc.ComplexEvent ) -> tuple[ComplexEventToAbjadContainer, ...]: complex_event_to_abjad_container_converters_cycle = itertools.cycle( self._complex_event_to_abjad_container_converters ) complex_event_to_abjad_container_converter_list = [] for _ in nested_complex_event_to_convert: complex_event_to_abjad_container_converter_list.append( next(complex_event_to_abjad_container_converters_cycle) ) return tuple(complex_event_to_abjad_container_converter_list)
[docs]class TagBasedNestedComplexEventToComplexEventToAbjadContainers( NestedComplexEventToComplexEventToAbjadContainers ): def __init__( self, tag_to_abjad_converter_dict: dict[str, ComplexEventToAbjadContainer], complex_event_to_tag: typing.Callable[ [core_events.abc.ComplexEvent], str ] = lambda complex_event: complex_event.tag, ): self._tag_to_abjad_converter_dict = tag_to_abjad_converter_dict self._complex_event_to_tag = complex_event_to_tag
[docs] def convert( self, nested_complex_event_to_convert: core_events.abc.ComplexEvent ) -> tuple[ComplexEventToAbjadContainer, ...]: complex_event_to_abjad_container_converter_list = [] for complex_event in nested_complex_event_to_convert: tag = self._complex_event_to_tag(complex_event) try: complex_event_to_abjad_container_converter = ( self._tag_to_abjad_converter_dict[tag] ) except KeyError: raise KeyError( f"Found undefined tag '{tag}'." " This object only knows the following tags:" f" '{self._tag_to_abjad_converter_dict.keys()}'" ) complex_event_to_abjad_container_converter_list.append( complex_event_to_abjad_container_converter ) return tuple(complex_event_to_abjad_container_converter_list)
[docs]class NestedComplexEventToAbjadContainer(ComplexEventToAbjadContainer): def __init__( self, nested_complex_event_to_complex_event_to_abjad_container_converters_converter: NestedComplexEventToComplexEventToAbjadContainers, abjad_container_class: typing.Type[abjad.Container], lilypond_type_of_abjad_container: str, complex_event_to_abjad_container_name: typing.Callable[ [core_events.abc.ComplexEvent], str ] = lambda complex_event: complex_event.tag, pre_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ] = tuple([]), post_process_abjad_container_routine_sequence: typing.Sequence[ abjad_converters.ProcessAbjadContainerRoutine ] = tuple([]), ): super().__init__( abjad_container_class, lilypond_type_of_abjad_container, complex_event_to_abjad_container_name, pre_process_abjad_container_routine_sequence, post_process_abjad_container_routine_sequence, ) self._nested_complex_event_to_complex_event_to_abjad_container_converters_converter = nested_complex_event_to_complex_event_to_abjad_container_converters_converter def _fill_abjad_container( self, abjad_container_to_fill: abjad.Container, nested_complex_event_to_convert: core_events.abc.ComplexEvent, ): complex_event_to_abjad_container_converter_tuple = self._nested_complex_event_to_complex_event_to_abjad_container_converters_converter.convert( nested_complex_event_to_convert ) for complex_event, complex_event_to_abjad_container_converter in zip( nested_complex_event_to_convert, complex_event_to_abjad_container_converter_tuple, ): converted_complex_event = ( complex_event_to_abjad_container_converter.convert(complex_event) ) abjad_container_to_fill.append(converted_complex_event)