"""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_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 SequentialEventToQuantizedAbjadContainer
from .quantization import NauertSequentialEventToQuantizedAbjadContainer
from .quantization import (
NauertSequentialEventToDurationLineBasedQuantizedAbjadContainer,
)
from .quantization import (
LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer,
)
__all__ = (
"ComplexEventToAbjadContainer",
"SequentialEventToAbjadVoice",
"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.SimultaneousEvent
)
}
)
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 SequentialEventToAbjadVoice(ComplexEventToAbjadContainer):
"""Convert :class:`~mutwo.core_events.SequentialEvent` to :class:`abjad.Voice`.
:param sequential_event_to_quantized_abjad_container: Class which
defines how the Mutwo data will be quantized. See
:class:`SequentialEventToQuantizedAbjadContainer` for more information.
:type sequential_event_to_quantized_abjad_container: SequentialEventToQuantizedAbjadContainer, optional
:param simple_event_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.events.music.NoteLike.pitch_list` attribute
(because by default :class:`mutwo.events.music.NoteLike` objects are expected).
When using different Event classes than :class:`~mutwo.events.music.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 simple_event_to_pitch_list: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Pitch], optional
:param simple_event_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.events.music.NoteLike.volume` attribute (because by default
:class:`mutwo.events.music.NoteLike` objects are expected).
When using different Event classes than :class:`~mutwo.events.music.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 simple_event_to_volume: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Volume], optional
:param simple_event_to_grace_note_sequential_event: Function to extract from a
:class:`mutwo.core_events.SimpleEvent` a
:class:`~mutwo.core_events.SequentialEvent`
object filled with
:class:`~mutwo.core_events.SimpleEvent`.
By default it asks the Event for its
:attr:`~mutwo.events.music.NoteLike.grace_note_sequential_event`
attribute (because by default :class:`mutwo.events.music.NoteLike`
objects are expected).
When using different Event classes than :class:`~mutwo.events.music.NoteLike`
with a different name for their `grace_note_sequential_event` property, this argument
should be overridden. If the
function call raises an :obj:`AttributeError` (e.g. if no grace_note_sequential_event can be
extracted), mutwo will use an empty
:class:`~mutwo.core_events.SequentialEvent`.
:type simple_event_to_grace_note_sequential_event: typing.Callable[[core_events.SimpleEvent], core_events.SequentialEvent[core_events.SimpleEvent]], optional
:param simple_event_to_after_grace_note_sequential_event: Function to extract from a
:class:`mutwo.core_events.SimpleEvent` a
:class:`~mutwo.core_events.SequentialEvent`
object filled with
:class:`~mutwo.core_events.SimpleEvent`.
By default it asks the Event for its
:attr:`~mutwo.events.music.NoteLike.after_grace_note_sequential_event`
attribute (because by default :class:`mutwo.events.music.NoteLike`
objects are expected).
When using different Event classes than :class:`~mutwo.events.music.NoteLike`
with a different name for their `after_grace_note_sequential_event` property, this
argument should be overridden. If the function call
raises an :obj:`AttributeError` (e.g. if no after_grace_note_sequential_event can be
extracted), mutwo will use an empty
:class:`~mutwo.core_events.SequentialEvent`.
:type simple_event_to_after_grace_note_sequential_event: typing.Callable[[core_events.SimpleEvent], core_events.SequentialEvent[core_events.SimpleEvent]], optional
:param simple_event_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.events.music.NoteLike.playing_indicator_collection`
attribute (because by default :class:`mutwo.events.music.NoteLike`
objects are expected).
When using different Event classes than :class:`~mutwo.events.music.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 simple_event_to_playing_indicator_collection: typing.Callable[[core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection,], optional
:param simple_event_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.events.music.NoteLike.notation_indicators`
(because by default :class:`mutwo.events.music.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 simple_event_to_notation_indicator_collection: typing.Callable[[core_events.SimpleEvent], music_parameters.NotationIndicatorCollection,], optional
:param simple_event_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.events.music.NoteLike.lyric` attribute (because by default
:class:`mutwo.events.music.NoteLike` objects are expected).
When using different Event classes than :class:`~mutwo.events.music.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 simple_event_to_lyric: typing.Callable[[core_events.SimpleEvent], music_parameters.abc.Lyric], optional
:param is_simple_event_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_simple_event_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.converters.frontends.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.converters.frontends.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 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
"""
ExtractedData = tuple[
list[music_parameters.abc.Pitch],
music_parameters.abc.Volume,
core_events.SequentialEvent[core_events.SimpleEvent],
core_events.SequentialEvent[core_events.SimpleEvent],
music_parameters.PlayingIndicatorCollection,
music_parameters.NotationIndicatorCollection,
music_parameters.abc.Lyric,
]
ExtractedDataPerSimpleEvent = tuple[ExtractedData, ...]
def __init__(
self,
sequential_event_to_quantized_abjad_container: SequentialEventToQuantizedAbjadContainer = NauertSequentialEventToQuantizedAbjadContainer(),
simple_event_to_pitch_list: typing.Callable[
[core_events.SimpleEvent], list[music_parameters.abc.Pitch]
] = music_converters.SimpleEventToPitchList(),
simple_event_to_volume: typing.Callable[
[core_events.SimpleEvent], music_parameters.abc.Volume
] = music_converters.SimpleEventToVolume(),
simple_event_to_grace_note_sequential_event: typing.Callable[
[core_events.SimpleEvent],
core_events.SequentialEvent[core_events.SimpleEvent],
] = music_converters.SimpleEventToGraceNoteSequentialEvent(),
simple_event_to_after_grace_note_sequential_event: typing.Callable[
[core_events.SimpleEvent],
core_events.SequentialEvent[core_events.SimpleEvent],
] = music_converters.SimpleEventToAfterGraceNoteSequentialEvent(),
simple_event_to_playing_indicator_collection: typing.Callable[
[core_events.SimpleEvent],
music_parameters.PlayingIndicatorCollection,
] = music_converters.SimpleEventToPlayingIndicatorCollection(),
simple_event_to_notation_indicator_collection: typing.Callable[
[core_events.SimpleEvent],
music_parameters.NotationIndicatorCollection,
] = music_converters.SimpleEventToNotationIndicatorCollection(),
simple_event_to_lyric: typing.Callable[
[core_events.SimpleEvent],
music_parameters.abc.Lyric,
] = music_converters.SimpleEventToLyric(),
is_simple_event_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(),
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([]),
):
# special treatment for duration line based quantizer
if isinstance(
sequential_event_to_quantized_abjad_container,
(
NauertSequentialEventToDurationLineBasedQuantizedAbjadContainer,
LeafMakerSequentialEventToDurationLineBasedQuantizedAbjadContainer,
),
):
post_process_abjad_container_routine_sequence = tuple(
post_process_abjad_container_routine_sequence
)
post_process_abjad_container_routine_sequence += (
abjad_converters.AddDurationLineEngraver(),
abjad_converters.PrepareForDurationLineBasedNotation(),
)
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_simple_event_rest is None:
def is_simple_event_rest(simple_event: core_events.SimpleEvent) -> bool:
pitch_list = core_utilities.call_function_except_attribute_error(
simple_event_to_pitch_list, simple_event, []
)
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._sequential_event_to_quantized_abjad_container = (
sequential_event_to_quantized_abjad_container
)
self._simple_event_to_pitch_list = simple_event_to_pitch_list
self._simple_event_to_volume = simple_event_to_volume
self._simple_event_to_grace_note_sequential_event = (
simple_event_to_grace_note_sequential_event
)
self._simple_event_to_after_grace_note_sequential_event = (
simple_event_to_after_grace_note_sequential_event
)
self._simple_event_to_playing_indicator_collection = (
simple_event_to_playing_indicator_collection
)
self._simple_event_to_notation_indicator_collection = (
simple_event_to_notation_indicator_collection
)
self._simple_event_to_lyric = simple_event_to_lyric
self._simple_event_to_function_tuple = (
self._simple_event_to_grace_note_sequential_event,
self._simple_event_to_after_grace_note_sequential_event,
self._simple_event_to_playing_indicator_collection,
self._simple_event_to_notation_indicator_collection,
self._simple_event_to_lyric,
)
self._is_simple_event_rest = is_simple_event_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
)
if tempo_envelope_to_abjad_attachment_tempo:
tempo_attachment_tuple = tempo_envelope_to_abjad_attachment_tempo.convert(
self._sequential_event_to_quantized_abjad_container.tempo_envelope
)
else:
tempo_attachment_tuple = None
self._tempo_attachment_tuple = tempo_attachment_tuple
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:
n_pitches = len(pitch_list)
if n_pitches == 0:
abjad_event_type = abjad.Rest
elif n_pitches == 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(abjad_voice).leaves():
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:
for bar in abjad_voice:
if all((isinstance(item, abjad.Rest) 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 _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
)
if abjad_attachment:
attachment_dict.update(
{abjad_attachment_class.get_class_name(): abjad_attachment}
)
return attachment_dict
def _grace_note_sequential_event_to_abjad_attachment(
self,
grace_note_sequential_event_or_after_grace_note_sequential_event: core_events.SequentialEvent[
core_events.SimpleEvent
],
is_before: bool,
) -> dict[str, abjad_parameters.abc.AbjadAttachment]:
if not grace_note_sequential_event_or_after_grace_note_sequential_event:
return {}
converter = _GraceNotesToAbjadVoiceConverter(
is_before,
self._simple_event_to_pitch_list,
self._simple_event_to_volume,
self._simple_event_to_playing_indicator_collection,
self._simple_event_to_notation_indicator_collection,
self._is_simple_event_rest,
self._mutwo_pitch_to_abjad_pitch,
)
grace_note_sequential_event_container = converter.convert(
grace_note_sequential_event_or_after_grace_note_sequential_event
)
if is_before:
name = "grace_note_sequential_event"
abjad_attachment_class = abjad_parameters.GraceNoteSequentialEvent
else:
name = "after_grace_note_sequential_event"
abjad_attachment_class = abjad_parameters.AfterGraceNoteSequentialEvent
return {name: abjad_attachment_class(grace_note_sequential_event_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,
) -> tuple[
tuple[
int,
typing.Union[
abjad_parameters.Tempo, abjad_parameters.DynamicChangeIndicationStop
],
],
...,
]:
absolute_time_per_leaf = (
SequentialEventToAbjadVoice._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 self._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_simple_event: 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_simple_event]
for attachment_name in self._available_attachment_tuple
}
for nth_event, extracted_data in enumerate(extracted_data_per_simple_event):
(
_,
volume,
grace_note_sequential_event,
after_grace_note_sequential_event,
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_sequential_event_to_abjad_attachment(
grace_note_sequential_event, True
)
)
abjad_parameters_for_nth_event.update(
self._grace_note_sequential_event_to_abjad_attachment(
after_grace_note_sequential_event, 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,
):
if self._tempo_attachment_tuple:
leaves = abjad.select(quanitisized_abjad_leaf_voice).leaves()
tempo_attachment_data = (
self._get_tempo_attachment_tuple_for_quantized_abjad_leaves(
quanitisized_abjad_leaf_voice
)
)
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_simple_event: tuple[
tuple[tuple[int, ...], ...], ...
],
abjad_parameters_per_type_per_event_tuple: tuple[
tuple[typing.Optional[abjad_parameters.abc.AbjadAttachment], ...], ...
],
) -> None:
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_simple_event,
abjad_parameters_per_type,
):
if attachment and attachment.is_active:
abjad_leaves = 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_leaves = attachment.process_leaf_tuple(
abjad_leaves, previous_attachment
)
for processed_abjad_leaf, index_tuple in zip(
processed_abjad_leaves, related_abjad_leaf_index_tuple_tuple
):
core_utilities.set_nested_item_from_index_sequence(
index_tuple,
quanitisized_abjad_leaf_voice,
processed_abjad_leaf,
)
previous_attachment = attachment
def _extract_pitch_list_and_volume_from_simple_event(
self, simple_event: core_events.SimpleEvent
) -> tuple[list[music_parameters.abc.Pitch], music_parameters.abc.Volume]:
pitch_list = self._simple_event_to_pitch_list(simple_event)
# TODO(Add option: no dynamic indicator if there aren't any pitches)
if pitch_list:
volume = self._simple_event_to_volume(simple_event)
if not volume.amplitude:
pitch_list = []
else:
volume = music_parameters.DirectVolume(0)
return pitch_list, volume
def _extract_data_from_simple_event(
self, simple_event: 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_simple_event(simple_event)
)
for function in self._simple_event_to_function_tuple:
extracted_data.append(function(simple_event)) # 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_simple_event: tuple[
tuple[tuple[int, ...], ...], ...
],
extracted_data_per_simple_event: ExtractedDataPerSimpleEvent,
is_simple_event_rest_per_simple_event: tuple[bool, ...],
):
for (
is_simple_event_rest,
extracted_data,
related_abjad_leaf_index_tuple_tuple,
) in zip(
is_simple_event_rest_per_simple_event,
extracted_data_per_simple_event,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
):
if not is_simple_event_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_simple_event: ExtractedDataPerSimpleEvent,
is_simple_event_rest_per_simple_event: tuple[bool, ...],
) -> str:
lyric_content_list = []
for extracted_data, is_simple_event_rest in zip(
extracted_data_per_simple_event, is_simple_event_rest_per_simple_event
):
if not is_simple_event_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 + " }",
format_slot="absolute_after",
),
voice_to_apply_lyrics_to,
)
def _quantize_sequential_event(
self,
sequential_event_to_convert: core_events.SequentialEvent[
core_events.SimpleEvent
],
is_simple_event_rest_per_simple_event: tuple[bool, ...],
) -> tuple[abjad.Container, tuple[tuple[tuple[int, ...], ...], ...],]:
is_simple_event_rest_per_simple_event_iterator = iter(
is_simple_event_rest_per_simple_event
)
(
quanitisized_abjad_leaf_voice,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
) = self._sequential_event_to_quantized_abjad_container.convert(
sequential_event_to_convert.set_parameter( # type: ignore
"is_rest",
lambda _: next(is_simple_event_rest_per_simple_event_iterator),
set_unassigned_parameter=True,
mutate=False, # type: ignore
)
)
return (
quanitisized_abjad_leaf_voice,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
)
def _fill_abjad_container(
self,
abjad_container_to_fill: abjad.Voice,
sequential_event_to_convert: core_events.SequentialEvent[
core_events.SimpleEvent
],
):
# tie rests before processing the event!
sequential_event_to_convert = sequential_event_to_convert.tie_by(
lambda event0, event1: self._is_simple_event_rest(event0)
and self._is_simple_event_rest(event1),
event_type_to_examine=core_events.SimpleEvent,
mutate=False, # type: ignore
)
# first, extract data from simple events and find rests
extracted_data_per_simple_event = tuple(
self._extract_data_from_simple_event(simple_event)
for simple_event in sequential_event_to_convert
)
is_simple_event_rest_per_simple_event = tuple(
self._is_simple_event_rest(simple_event)
for simple_event in sequential_event_to_convert
)
# second, quantize the sequential event
(
quanitisized_abjad_leaf_voice,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
) = self._quantize_sequential_event(
sequential_event_to_convert, is_simple_event_rest_per_simple_event
)
# third, apply pitches on Abjad voice
self._apply_pitches_on_quantized_abjad_leaves(
quanitisized_abjad_leaf_voice,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
extracted_data_per_simple_event,
is_simple_event_rest_per_simple_event,
)
# 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_simple_event
)
)
self._apply_tempos_on_quantized_abjad_leaves(quanitisized_abjad_leaf_voice)
self._apply_abjad_parameters_on_quantized_abjad_leaves(
quanitisized_abjad_leaf_voice,
related_abjad_leaf_index_tuple_tuple_per_simple_event,
abjad_parameters_per_type_per_event,
)
# fifth, replace rests lasting one bar with full measure rests
if self._write_multimeasure_rests:
SequentialEventToAbjadVoice._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_simple_event, is_simple_event_rest_per_simple_event
)
self._apply_lyrics_on_voice(abjad_container_to_fill, lyric_content)
# ###################################################################### #
# public methods for interaction with the user #
# ###################################################################### #
[docs] def convert(
self,
sequential_event_to_convert: core_events.SequentialEvent[
core_events.SimpleEvent
],
) -> abjad.Voice:
"""Convert passed :class:`~mutwo.core_events.SequentialEvent`.
:param sequential_event_to_convert: The
:class:`~mutwo.core_events.SequentialEvent` which shall
be converted to the :class:`abjad.Voice` object.
:type sequential_event_to_convert: mutwo.core_events.SequentialEvent
**Example:**
>>> import abjad
>>> from mutwo.events import basic, music
>>> from mutwo.converters.frontends import abjad as mutwo_abjad
>>> mutwo_melody = basic.SequentialEvent(
>>> [
>>> music.NoteLike(pitch, duration)
>>> for pitch, duration in zip("c a g e".split(" "), (1, 1 / 6, 1 / 6, 1 / 6))
>>> ]
>>> )
>>> converter = mutwo_abjad.SequentialEventToAbjadVoice()
>>> 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(sequential_event_to_convert)
class _GraceNotesToAbjadVoiceConverter(SequentialEventToAbjadVoice):
class GraceNotesToQuantizedAbjadContainerConverter(core_converters.abc.Converter):
def convert(
self, sequential_event_to_convert: core_events.SequentialEvent
) -> abjad.Container:
container = abjad.Container([], simultaneous=False)
indices = []
for nth_event, event in enumerate(sequential_event_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,
simple_event_to_pitch_list: typing.Callable[
[core_events.SimpleEvent], list[music_parameters.abc.Pitch]
],
simple_event_to_volume: typing.Callable[
[core_events.SimpleEvent], music_parameters.abc.Volume
],
simple_event_to_playing_indicator_collection: typing.Callable[
[core_events.SimpleEvent],
music_parameters.PlayingIndicatorCollection,
],
simple_event_to_notation_indicator_collection: typing.Callable[
[core_events.SimpleEvent],
music_parameters.NotationIndicatorCollection,
],
is_simple_event_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__(
sequential_event_to_quantized_abjad_container=self.GraceNotesToQuantizedAbjadContainerConverter(),
simple_event_to_pitch_list=simple_event_to_pitch_list,
simple_event_to_volume=simple_event_to_volume,
simple_event_to_playing_indicator_collection=simple_event_to_playing_indicator_collection,
simple_event_to_notation_indicator_collection=simple_event_to_notation_indicator_collection,
is_simple_event_rest=is_simple_event_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,
simple_event_to_grace_note_sequential_event=lambda _: core_events.SequentialEvent(
[]
),
simple_event_to_after_grace_note_sequential_event=lambda _: core_events.SequentialEvent(
[]
),
write_multimeasure_rests=False,
abjad_container_class=abjad_container_class,
lilypond_type_of_abjad_container=None,
)
def _grace_note_sequential_event_to_abjad_attachment(
self,
grace_note_sequential_event_or_after_grace_note_sequential_event: core_events.SequentialEvent[
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)