Source code for mutwo.music_converters.playing_indicators

"""Apply :class:`~mutwo.music_parameters.abc.PlayingIndicator` on :class:`~mutwo.core_events.abc.Event`.

"""

import abc
import copy
import itertools
import typing

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

import numpy as np

from mutwo import core_constants
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 mutwo import music_utilities


__all__ = (
    "PlayingIndicatorConverter",
    "ArpeggioConverter",
    "StacattoConverter",
    "ArticulationConverter",
    "TrillConverter",
    "OptionalConverter",
    "PlayingIndicatorsConverter",
)


[docs]class PlayingIndicatorConverter(core_converters.abc.Converter): """Abstract base class to apply :class:`~mutwo.music_parameters.abc.PlayingIndicator` on a :class:`~mutwo.core_events.SimpleEvent`. :param chronon_to_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 To write a new PlayingIndicatorConverter the abstract method :func:`_apply_playing_indicator` and the abstract properties `playing_indicator_name` and `default_playing_indicator` have to be overridden. """ def __init__( self, chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), # type: ignore ): self._chronon_to_playing_indicator_collection = ( chronon_to_playing_indicator_collection ) @abc.abstractmethod def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, playing_indicator: music_parameters.abc.PlayingIndicator, ) -> core_events.Consecution[core_events.SimpleEvent]: ... @property @abc.abstractmethod def playing_indicator_name(self) -> str: ... @property @abc.abstractmethod def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: ...
[docs] def convert( self, chronon_to_convert: core_events.SimpleEvent ) -> core_events.Consecution[core_events.SimpleEvent]: """Apply PlayingIndicator on chronon. :param chronon_to_convert: The event which shall be converted. :type chronon_to_convert: core_events.SimpleEvent """ playing_indicator_collection = ( self._chronon_to_playing_indicator_collection( chronon_to_convert, ) ) playing_indicator = core_utilities.call_function_except_attribute_error( lambda playing_indicator_collection: getattr( playing_indicator_collection, self.playing_indicator_name ), playing_indicator_collection, self.default_playing_indicator, ) if playing_indicator.is_active: return self._apply_playing_indicator( chronon_to_convert, playing_indicator ) else: return core_events.Consecution([copy.deepcopy(chronon_to_convert)])
[docs]class ArpeggioConverter(PlayingIndicatorConverter): """Apply arpeggio on :class:`~mutwo.core_events.SimpleEvent`. :param duration_for_each_attack: Set how long each attack of the Arpeggio lasts. Default to 0.1. :type duration_for_each_attack: constants.DurationType :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_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 set_pitch_list_for_chronon: Function which assigns a list of :class:`~mutwo.music_parameters.abc.Pitch` objects to a :class:`~mutwo.core_events.SimpleEvent`. By default the function assigns the passed pitches to the :attr:`~mutwo.music_events.NoteLike.pitch_list` attribute (because by default :class:`mutwo.music_events.NoteLike` objects are expected). :type set_pitch_list_for_chronon: typing.Callable[[core_events.SimpleEvent, list[music_parameters.abc.Pitch]], None] """ def __init__( self, duration_for_each_attack: core_constants.DurationType = 0.1, chronon_to_pitch_list: typing.Callable[ [core_events.SimpleEvent], list[music_parameters.abc.Pitch] ] = music_converters.SimpleEventToPitchList(), chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), set_pitch_list_for_chronon: typing.Callable[ [core_events.SimpleEvent, list[music_parameters.abc.Pitch]], None ] = lambda chronon, pitch_list: chronon.set_parameter( # type: ignore "pitch_list", pitch_list, set_unassigned_parameter=True ), ): super().__init__( chronon_to_playing_indicator_collection=chronon_to_playing_indicator_collection ) self._duration_for_each_attack = ( core_events.configurations.UNKNOWN_OBJECT_TO_DURATION( duration_for_each_attack ) ) self._chronon_to_pitch_list = chronon_to_pitch_list self._set_pitch_list_for_chronon = set_pitch_list_for_chronon @property def playing_indicator_name(self) -> str: return "arpeggio" @property def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: return music_parameters.Arpeggio() def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, playing_indicator: music_parameters.Arpeggio, ) -> core_events.Consecution[core_events.SimpleEvent]: pitch_list = list(self._chronon_to_pitch_list(chronon_to_convert)) # sort pitches according to Arpeggio direction pitch_list.sort(reverse=playing_indicator.direction != "up") converted_event: core_events.Consecution[ core_events.SimpleEvent ] = core_events.Consecution( [copy.copy(chronon_to_convert) for _ in pitch_list] ) # apply pitches on events for event_index, pitch in enumerate(pitch_list): self._set_pitch_list_for_chronon(converted_event[event_index], [pitch]) # set correct duration for each event event_count = len(converted_event) duration_of_each_attack = self._duration_for_each_attack if duration_of_each_attack * event_count > chronon_to_convert.duration: duration_of_each_attack = chronon_to_convert.duration / event_count for event_index in range(event_count - 1): converted_event[event_index].duration = duration_of_each_attack converted_event[-1].duration -= ( converted_event.duration - chronon_to_convert.duration ) return converted_event
[docs]class StacattoConverter(PlayingIndicatorConverter): """Apply staccato on :class:`~mutwo.core_events.SimpleEvent`. :param factor: :param allowed_articulation_name_sequence: :param chronon_to_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 """ def __init__( self, factor: float = 0.5, allowed_articulation_name_sequence: typing.Sequence[str] = ("staccato", "."), chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), ): self._allowed_articulation_name_sequence = allowed_articulation_name_sequence self._factor = factor super().__init__(chronon_to_playing_indicator_collection) def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, _: music_parameters.abc.PlayingIndicator, ) -> core_events.Consecution[core_events.SimpleEvent]: duration = chronon_to_convert.duration * self._factor consecution = core_events.Consecution( [ chronon_to_convert.set_parameter( "duration", duration, mutate=False ), core_events.SimpleEvent(duration), ] ) return consecution @property def playing_indicator_name(self) -> str: return "articulation" @property def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: return music_parameters.Articulation()
[docs]class ArticulationConverter(PlayingIndicatorConverter): """Apply articulation on :class:`~mutwo.core_events.SimpleEvent`. :param articulation_name_tuple_to_playing_indicator_converter: :type articulation_name_tuple_to_playing_indicator_converter: dict[tuple[str, ...], PlayingIndicatorConverter] :param chronon_to_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 """ def __init__( self, articulation_name_tuple_to_playing_indicator_converter: dict[ tuple[str, ...], PlayingIndicatorConverter ] = {("staccato", "."): StacattoConverter()}, chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), ): self._logger = core_utilities.get_cls_logger(type(self)) articulation_name_to_playing_indicator_converter = {} for ( articulation_name_tuple, playing_indicator_converter, ) in articulation_name_tuple_to_playing_indicator_converter.items(): for articulation_name in articulation_name_tuple: if ( articulation_name not in articulation_name_to_playing_indicator_converter ): self._logger.warning( music_utilities.DuplicatePlayingIndicatorConverterMappingWarning( articulation_name, playing_indicator_converter ) ) articulation_name_to_playing_indicator_converter.update( {articulation_name: playing_indicator_converter} ) self._articulation_name_to_playing_indicator_converter = ( articulation_name_to_playing_indicator_converter ) super().__init__(chronon_to_playing_indicator_collection) def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, playing_indicator: music_parameters.Articulation, ) -> core_events.Consecution[core_events.SimpleEvent]: if ( playing_indicator.name in self._articulation_name_to_playing_indicator_converter ): return self._articulation_name_to_playing_indicator_converter[ playing_indicator.name ].convert(chronon_to_convert) else: return core_events.Consecution([copy.deepcopy(chronon_to_convert)]) @property def playing_indicator_name(self) -> str: return "articulation" @property def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: return music_parameters.Articulation()
[docs]class TrillConverter(PlayingIndicatorConverter): """Apply trill on :class:`~mutwo.core_events.SimpleEvent`. :param trill_size: :type trill_size: constants.DurationType :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_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 """ def __init__( self, trill_size: core_constants.DurationType = fractions.Fraction(1, 16), chronon_to_pitch_list: typing.Callable[ [core_events.SimpleEvent], list[music_parameters.abc.Pitch] ] = music_converters.SimpleEventToPitchList(), chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), ): self._trill_size = trill_size self._chronon_to_pitch_list = chronon_to_pitch_list super().__init__(chronon_to_playing_indicator_collection) def _apply_trill( self, chronon_to_convert: core_events.SimpleEvent, trill: music_parameters.Trill, pitch_list: list[music_parameters.abc.Pitch], ) -> core_events.Consecution[core_events.SimpleEvent]: trill_item_count = chronon_to_convert.duration // self._trill_size remaining = chronon_to_convert.duration - ( trill_item_count * self._trill_size ) consecution = core_events.Consecution([]) pitch_cycle = itertools.cycle((pitch_list, trill.pitch)) for _ in range(int(trill_item_count)): chronon = chronon_to_convert.set_parameter( "duration", self._trill_size, mutate=False ).set_parameter("pitch_list", next(pitch_cycle)) consecution.append(chronon) consecution[-1].duration += remaining return consecution def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, playing_indicator: music_parameters.Trill, ) -> core_events.Consecution[core_events.SimpleEvent]: pitch_list = self._chronon_to_pitch_list(chronon_to_convert) if pitch_list: return self._apply_trill( chronon_to_convert, playing_indicator, pitch_list ) else: return core_events.Consecution([copy.copy(chronon_to_convert)]) @property def playing_indicator_name(self) -> str: return "trill" @property def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: return music_parameters.Trill()
[docs]class OptionalConverter(PlayingIndicatorConverter): """Apply optional on :class:`~mutwo.core_events.SimpleEvent`. :param likelihood: A number between 0 - 1. 1 means that each optional note is played, 0 means no optional note is played. Default to 0.5. :type likelihood: float :param random_seed: Set inner random process. Default to 100. :type random_seed: int :param make_rest: A function which takes the original :class:`~mutwo.core_events.SimpleEvent` and returns a new `SimpleEvent` with the same duration which represents a rest. By default, `mutwo` simply creates a `SimpleEvent` with the same duration. :type make_rest: typing.Callable[[core_events.SimpleEvent], core_events.SimpleEvent] :param chronon_to_playing_indicator_collection: Function to extract from a :class:`mutwo.core_events.SimpleEvent` a :class:`mutwo.music_parameters.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_indicator_collection 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 """ def __init__( self, likelihood: float = 0.5, random_seed: int = 100, make_rest: typing.Callable[ [core_events.SimpleEvent], core_events.SimpleEvent ] = lambda chronon: core_events.SimpleEvent(chronon.duration), chronon_to_playing_indicator_collection: typing.Callable[ [core_events.SimpleEvent], music_parameters.PlayingIndicatorCollection, ] = music_converters.SimpleEventToPlayingIndicatorCollection(), ): self._make_rest = make_rest self._likelihood = likelihood self._random = np.random.default_rng(random_seed) super().__init__(chronon_to_playing_indicator_collection) def _apply_playing_indicator( self, chronon_to_convert: core_events.SimpleEvent, playing_indicator: music_parameters.abc.ExplicitPlayingIndicator, ) -> core_events.Consecution[core_events.SimpleEvent]: consecution = core_events.Consecution([]) if playing_indicator.is_active and self._random.random() > self._likelihood: rest = self._make_rest(chronon_to_convert) consecution.append(rest) else: consecution.append(chronon_to_convert.copy()) return consecution @property def playing_indicator_name(self) -> str: return "optional" @property def default_playing_indicator(self) -> music_parameters.abc.PlayingIndicator: return music_parameters.abc.ExplicitPlayingIndicator()
[docs]class PlayingIndicatorsConverter(core_converters.abc.SymmetricalEventConverter): """Apply :class:`mutwo.music_parameters.abc.PlayingIndicator` on any :class:`~mutwo.core_events.abc.Event`. :param playing_indicator_converter_sequence: A sequence of :class:`PlayingIndicatorConverter` which shall be applied on each :class:`~mutwo.core_events.SimpleEvent`. :type playing_indicator_converter_sequence: typing.Sequence[PlayingIndicatorConverter] """ def __init__( self, playing_indicator_converter_sequence: typing.Sequence[ PlayingIndicatorConverter ], ): self._playing_indicator_converter_tuple = tuple( playing_indicator_converter_sequence ) def _convert_chronon( self, event_to_convert: core_events.SimpleEvent, _: core_constants.DurationType, ) -> core_events.Consecution: """Convert instance of :class:`mutwo.core_events.SimpleEvent`.""" converted_event = [event_to_convert] for playing_indicator_converter in self._playing_indicator_converter_tuple: new_converted_event: list[core_events.SimpleEvent] = [] for chronon in converted_event: converted_chronon = playing_indicator_converter.convert( chronon ) new_converted_event.extend(converted_chronon) converted_event = new_converted_event return core_events.Consecution(converted_event)
[docs] def convert(self, event_to_convert: core_events.abc.Event) -> core_events.abc.Event: converted_event = self._convert_event(event_to_convert, 0) return converted_event