Source code for mutwo.music_parameters.pitches.EqualDividedOctavePitch

from __future__ import annotations

import operator
import typing


from mutwo import core_constants
from mutwo import core_utilities
from mutwo import music_parameters


__all__ = ("EqualDividedOctavePitch",)

ConcertPitch = core_constants.Real | music_parameters.abc.Pitch


[docs]class EqualDividedOctavePitch(music_parameters.abc.Pitch): """Pitch that is tuned to an Equal divided octave tuning system. :param n_pitch_classes_per_octave: how many pitch classes in each octave occur (for instance 12 for a chromatic system, 24 for quartertones, etc.) :param pitch_class: The pitch class of the new :class:`EqualDividedOctavePitch` object. :param octave: The octave of the new :class:`EqualDividedOctavePitch` object (where 0 is the middle octave, 1 is one octave higher and -1 is one octave lower). :param concert_pitch_pitch_class: The pitch class of the reference pitch (for instance 9 in a chromatic 12 tone system where `a` should be the reference pitch). :param concert_pitch_octave: The octave of the reference pitch. :param concert_pitch: The frequency of the reference pitch (for instance 440 for a). >>> from mutwo import music_parameters >>> # making a middle `a` >>> pitch = music_parameters.EqualDividedOctavePitch(12, 9, 4, 9, 4, 440) """ def __init__( self, n_pitch_classes_per_octave: int, pitch_class: core_constants.Real, octave: int, concert_pitch_pitch_class: core_constants.Real, concert_pitch_octave: int, concert_pitch: ConcertPitch = None, *args, **kwargs, ): super().__init__( *args, **kwargs, ) concert_pitch = ( concert_pitch or music_parameters.configurations.DEFAULT_CONCERT_PITCH ) self._n_pitch_classes_per_octave = n_pitch_classes_per_octave self.pitch_class = pitch_class self.octave = octave self.concert_pitch_pitch_class = concert_pitch_pitch_class self.concert_pitch_octave = concert_pitch_octave self.concert_pitch = concert_pitch # type: ignore # ###################################################################### # # magic methods # # ###################################################################### # def __sub__(self, other: EqualDividedOctavePitch) -> core_constants.Real: """Calculates the interval between two ``EqualDividedOctave`` pitches.""" try: assert self.n_pitch_classes_per_octave == other.n_pitch_classes_per_octave except AssertionError: raise ValueError( "Can't calculate the interval between to different" " EqualDividedOctavePitch objects with different value for" " 'n_pitch_classes_per_octave'." ) n_pitch_classes_difference = self.pitch_class - other.pitch_class n_octaves_difference = self.octave - other.octave return n_pitch_classes_difference + ( n_octaves_difference * self.n_pitch_classes_per_octave ) # ###################################################################### # # private methods # # ###################################################################### # def _assert_correct_pitch_class(self, pitch_class: core_constants.Real) -> None: """Makes sure the respective pitch_class is within the allowed range.""" try: assert all( (pitch_class <= self.n_pitch_classes_per_octave - 1, pitch_class >= 0) ) except AssertionError: raise ValueError( f"Invalid pitch class {pitch_class}!. " "Pitch_class has to be in range (min = 0, max" f" = {self.n_pitch_classes_per_octave - 1})." ) def _fetch_n_pitch_classes_difference( self, pitch_interval: music_parameters.abc.PitchInterval | core_constants.Real, ) -> core_constants.Real: if isinstance( pitch_interval, music_parameters.abc.PitchInterval, ): return pitch_interval.interval / self.n_cents_per_step else: return pitch_interval def _math( self, n_pitch_classes_difference: core_constants.Real, operator: typing.Callable[ [core_constants.Real, core_constants.Real], core_constants.Real ], ) -> None: new_pitch_class = operator(self.pitch_class, n_pitch_classes_difference) n_octaves_difference = new_pitch_class // self.n_pitch_classes_per_octave new_pitch_class = new_pitch_class % self.n_pitch_classes_per_octave new_octave = self.octave + n_octaves_difference self.pitch_class = new_pitch_class self.octave = int(new_octave) # ###################################################################### # # public properties # # ###################################################################### # @property def n_pitch_classes_per_octave(self) -> int: """Defines in how many different pitch classes one octave get divided.""" return self._n_pitch_classes_per_octave @property def concert_pitch(self) -> music_parameters.abc.Pitch: """The referential concert pitch for the respective pitch object.""" return self._concert_pitch @concert_pitch.setter def concert_pitch(self, concert_pitch: ConcertPitch) -> None: if not isinstance(concert_pitch, music_parameters.abc.Pitch): concert_pitch = music_parameters.DirectPitch(concert_pitch) self._concert_pitch = concert_pitch @property def concert_pitch_pitch_class(self) -> core_constants.Real: """The pitch class of the referential concert pitch.""" return self._concert_pitch_pitch_class @concert_pitch_pitch_class.setter def concert_pitch_pitch_class(self, pitch_class: core_constants.Real) -> None: self._assert_correct_pitch_class(pitch_class) self._concert_pitch_pitch_class = pitch_class @property def pitch_class(self) -> core_constants.Real: """The pitch class of the pitch.""" return self._pitch_class @pitch_class.setter def pitch_class(self, pitch_class: core_constants.Real) -> None: self._assert_correct_pitch_class(pitch_class) self._pitch_class = pitch_class @property def step_factor(self): """The factor with which to multiply a frequency to reach the next pitch.""" return pow(2, 1 / self.n_pitch_classes_per_octave) @property def n_cents_per_step(self) -> float: """This property describes how many cents are between two adjacent pitches.""" return self.ratio_to_cents(self.step_factor) @property def frequency(self) -> float: n_octaves_distant_to_concert_pitch = self.octave - self.concert_pitch_octave n_pitch_classes_distant_to_concert_pitch = ( self.pitch_class - self.concert_pitch_pitch_class ) distance_to_concert_pitch_in_cents = ( n_octaves_distant_to_concert_pitch * music_parameters.constants.OCTAVE_IN_CENTS ) + (self.n_cents_per_step * n_pitch_classes_distant_to_concert_pitch) distance_to_concert_pitch_as_factor = self.cents_to_ratio( distance_to_concert_pitch_in_cents ) return core_utilities.round_floats( self.concert_pitch.frequency * distance_to_concert_pitch_as_factor, music_parameters.configurations.EQUAL_DIVIDED_OCTAVE_PITCH_ROUND_FREQUENCY_DIGIT_COUNT, ) # ###################################################################### # # public methods # # ###################################################################### #
[docs] @core_utilities.add_copy_option def add( # type: ignore self, pitch_interval: music_parameters.abc.PitchInterval | core_constants.Real ) -> EqualDividedOctavePitch: # type: ignore """Transposes the ``EqualDividedOctavePitch`` by n_pitch_classes_difference.""" n_pitch_classes_difference = self._fetch_n_pitch_classes_difference( pitch_interval ) self._math(n_pitch_classes_difference, operator.add)
[docs] @core_utilities.add_copy_option def subtract( # type: ignore self, pitch_interval: music_parameters.abc.PitchInterval | core_constants.Real ) -> EqualDividedOctavePitch: # type: ignore """Transposes the ``EqualDividedOctavePitch`` by n_pitch_classes_difference.""" if isinstance( pitch_interval, music_parameters.abc.PitchInterval, ): return super().subtract(pitch_interval) # type: ignore else: return self.add(-pitch_interval)