Source code for mutwo.abjad_converters.parameters.tempos

import abc
import typing

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

from mutwo import abjad_parameters
from mutwo import core_converters
from mutwo import core_constants
from mutwo import core_events
from mutwo import core_parameters
from mutwo import core_utilities

__all__ = (
    "TempoEnvelopeToAbjadAttachmentTempo",
    "ComplexTempoEnvelopeToAbjadAttachmentTempo",
)


[docs]class TempoEnvelopeToAbjadAttachmentTempo(core_converters.abc.Converter): """Convert tempo envelope to :class:`~mutwo.abjad_parameters.Tempo`. Abstract base class for tempo envelope conversion. See :class:`ComplexTempoEnvelopeToAbjadAttachmentTempo` for a concrete class. """
[docs] @abc.abstractmethod def convert( self, tempo_envelope_to_convert: core_events.TempoEnvelope ) -> tuple[tuple[core_constants.Real, abjad_parameters.Tempo], ...]: # return tuple filled with subtuples (leaf_index, abjad_parameters.Tempo) raise NotImplementedError()
[docs]class ComplexTempoEnvelopeToAbjadAttachmentTempo(TempoEnvelopeToAbjadAttachmentTempo): """Convert tempo envelope to :class:`~mutwo.abjad_parameters.Tempo`. This object tries to intelligently set correct tempo abjad_parameters to an :class:`abjad.Voice` object, appropriate to Western notation standards. Therefore it will not repeat tempo indications if they are merely repetitions of previous tempo indications and it will write 'a tempo' when returning to the same tempo after ritardandi or accelerandi. """ # ###################################################################### # # private static methods # # ###################################################################### # @staticmethod def _convert_tempo_point_tuple( tempo_point_tuple: tuple[ typing.Union[core_constants.Real, core_parameters.abc.TempoPoint], ... ] ) -> tuple[core_parameters.abc.TempoPoint, ...]: return tuple( tempo_point if isinstance(tempo_point, core_parameters.abc.TempoPoint) else core_parameters.DirectTempoPoint(float(tempo_point)) for tempo_point in tempo_point_tuple ) @staticmethod def _find_dynamic_change_indication( tempo_point: core_parameters.abc.TempoPoint, next_tempo_point: typing.Optional[core_parameters.abc.TempoPoint], ) -> typing.Optional[str]: dynamic_change_indication = None if next_tempo_point: absolute_tempo_for_current_tempo_point = ( tempo_point.absolute_tempo_in_beats_per_minute ) absolute_tempo_for_next_tempo_point = ( next_tempo_point.absolute_tempo_in_beats_per_minute ) if ( absolute_tempo_for_current_tempo_point > absolute_tempo_for_next_tempo_point ): dynamic_change_indication = "rit." elif ( absolute_tempo_for_current_tempo_point < absolute_tempo_for_next_tempo_point ): dynamic_change_indication = "acc." return dynamic_change_indication @staticmethod def _shall_write_metronome_mark( tempo_envelope_to_convert: core_events.TempoEnvelope, tempo_point_index: int, tempo_point: core_parameters.abc.TempoPoint, tempo_point_tuple: tuple[core_parameters.abc.TempoPoint, ...], ) -> bool: write_metronome_mark = True for previous_tempo_point, previous_tempo_point_duration in zip( reversed(tempo_point_tuple[:tempo_point_index]), reversed( tempo_envelope_to_convert.get_parameter("duration")[:tempo_point_index] ), ): # make sure the previous tempo point could have been written # down (longer duration than minimal duration) if previous_tempo_point_duration > 0: # if the previous writeable MetronomeMark has the same # beats per minute than the current event, there is no # need to write it down again if ( previous_tempo_point.absolute_tempo_in_beats_per_minute == tempo_point.absolute_tempo_in_beats_per_minute ): write_metronome_mark = False break # but if it differs, we should definitely write it down else: break return write_metronome_mark @staticmethod def _shall_stop_dynamic_change_indication( tempo_attachment_tuple: tuple[ tuple[core_constants.Real, abjad_parameters.Tempo], ... ] ) -> bool: stop_dynamic_change_indicaton = False for _, previous_tempo_attachment in reversed(tempo_attachment_tuple): # make sure the previous tempo point could have been written # down (longer duration than minimal duration) if previous_tempo_attachment.dynamic_change_indication is not None: stop_dynamic_change_indicaton = True break return stop_dynamic_change_indicaton @staticmethod def _find_metronome_mark_values( write_metronome_mark: bool, tempo_point: core_parameters.abc.TempoPoint, stop_dynamic_change_indicaton: bool, ) -> tuple[ typing.Optional[tuple[int, int]], typing.Optional[typing.Union[int, tuple[int, int]]], typing.Optional[str], ]: if write_metronome_mark: textual_indication: typing.Optional[str] = tempo_point.textual_indication reference = fractions.Fraction(tempo_point.reference) * fractions.Fraction( 1, 4 ) reference_duration: typing.Optional[tuple[int, int]] = ( reference.numerator, reference.denominator, ) units_per_minute: typing.Optional[typing.Union[int, tuple[int, int]]] = ( ( int(tempo_point.tempo_or_tempo_range_in_beats_per_minute[0]), int(tempo_point.tempo_or_tempo_range_in_beats_per_minute[1]), ) if isinstance( tempo_point.tempo_or_tempo_range_in_beats_per_minute, tuple ) else int(tempo_point.tempo_or_tempo_range_in_beats_per_minute) ) else: reference_duration = None units_per_minute = None # check if you can write 'a tempo' if stop_dynamic_change_indicaton: textual_indication = "a tempo" else: textual_indication = None return reference_duration, units_per_minute, textual_indication @staticmethod def _process_tempo_chronon( tempo_envelope_to_convert: core_events.TempoEnvelope, tempo_point_index: int, tempo_point: core_parameters.abc.TempoPoint, tempo_point_tuple: tuple[core_parameters.abc.TempoPoint, ...], tempo_attachment_tuple: tuple[ tuple[core_constants.Real, abjad_parameters.Tempo], ... ], ) -> abjad_parameters.Tempo: try: next_tempo_point: typing.Optional[ core_parameters.abc.TempoPoint ] = tempo_point_tuple[tempo_point_index + 1] except IndexError: next_tempo_point = None # check for dynamic_change_indication dynamic_change_indication = ( ComplexTempoEnvelopeToAbjadAttachmentTempo._find_dynamic_change_indication( tempo_point, next_tempo_point ) ) write_metronome_mark = ( ComplexTempoEnvelopeToAbjadAttachmentTempo._shall_write_metronome_mark( tempo_envelope_to_convert, tempo_point_index, tempo_point, tempo_point_tuple, ) ) stop_dynamic_change_indicaton = ComplexTempoEnvelopeToAbjadAttachmentTempo._shall_stop_dynamic_change_indication( tempo_attachment_tuple ) ( reference_duration, units_per_minute, textual_indication, ) = ComplexTempoEnvelopeToAbjadAttachmentTempo._find_metronome_mark_values( write_metronome_mark, tempo_point, stop_dynamic_change_indicaton ) # for writing 'a tempo' if textual_indication == "a tempo": write_metronome_mark = True converted_tempo_point = abjad_parameters.Tempo( reference_duration=reference_duration, units_per_minute=units_per_minute, textual_indication=textual_indication, dynamic_change_indication=dynamic_change_indication, stop_dynamic_change_indicaton=stop_dynamic_change_indicaton, print_metronome_mark=write_metronome_mark, ) return converted_tempo_point # ###################################################################### # # public api # # ###################################################################### #
[docs] def convert( self, tempo_envelope_to_convert: core_events.TempoEnvelope ) -> tuple[tuple[core_constants.Real, abjad_parameters.Tempo], ...]: tempo_point_tuple = ( ComplexTempoEnvelopeToAbjadAttachmentTempo._convert_tempo_point_tuple( tuple(tempo_envelope_to_convert.value_tuple) ) ) tempo_attachment_list: list[ tuple[core_constants.Real, abjad_parameters.Tempo] ] = [] for tempo_point_index, absolute_time, duration, tempo_point in zip( range(len(tempo_point_tuple)), core_utilities.accumulate_from_n( tempo_envelope_to_convert.get_parameter("duration"), core_parameters.DirectDuration(0), ), tuple(tempo_envelope_to_convert.get_parameter("duration")) + (1,), tempo_point_tuple, ): tempo_attachment = ( ComplexTempoEnvelopeToAbjadAttachmentTempo._process_tempo_chronon( tempo_envelope_to_convert, tempo_point_index, tempo_point, tempo_point_tuple, tuple(tempo_attachment_list), ) ) tempo_attachment_list.append((absolute_time, tempo_attachment)) return tuple(tempo_attachment_list)