Source code for mutwo.abjad_converters.parameters.heji

import typing

import abjad
import quicktions as fractions

from mutwo import ekmelily_converters
from mutwo import music_parameters

from .pitches import MutwoPitchToAbjadPitch


__all__ = ("MutwoPitchToHEJIAbjadPitch",)


[docs]class MutwoPitchToHEJIAbjadPitch(MutwoPitchToAbjadPitch): """Convert :class:`~mutwo.music_parameters.JustIntonationPitch` to abjad pitches. :param reference_pitch: The reference pitch (1/1). Should be a diatonic pitch name (see :const:`~mutwo.music_parameters.constants.DIATONIC_PITCH_CLASS_CONTAINER`) in English nomenclature. For any other reference pitch than 'c', Lilyponds midi rendering for pitches with the diatonic pitch 'c' will be slightly out of tune (because the first value of :arg:`global_scale` always have to be 0). :type reference_pitch: str, optional :param prime_to_heji_accidental_name: Mapping of a prime number to a string which indicates the respective prime number in the resulting accidental name. See :const:`mutwo.ekmelily_converters.configurations.DEFAULT_PRIME_TO_HEJI_ACCIDENTAL_NAME_DICT` for the default mapping. :type prime_to_heji_accidental_name: dict[int, str], optional :param otonality_indicator: String which indicates that the respective prime alteration is otonal. See :const:`mutwo.ekmelily_converters.configurations.DEFAULT_OTONALITY_INDICATOR` for the default value. :type otonality_indicator: str, optional :param utonality_indicator: String which indicates that the respective prime alteration is utonal. See :const:`mutwo.ekmelily_converters.configurations.DEFAULT_OTONALITY_INDICATOR` for the default value. :type utonality_indicator: str, optional :param exponent_to_exponent_indicator: Function to convert the exponent of a prime number to string which indicates the respective exponent. See :func:`mutwo.ekmelily_converters.configurations.DEFAULT_EXPONENT_TO_EXPONENT_INDICATOR` for the default function. :type exponent_to_exponent_indicator: typing.Callable[[int], str], optional :param tempered_pitch_indicator: String which indicates that the respective accidental is tempered (12 EDO). See :const:`mutwo.ekmelily_converters.configurations.DEFAULT_TEMPERED_PITCH_INDICATOR` for the default value. :type tempered_pitch_indicator: str, optional The resulting Abjad pitches are expected to be used in combination with tuning files that are generated by :class:`mutwo.ekmelily_converters.HEJIEkmelilyTuningFileConverter` and with the Lilypond extension `Ekmelily <http://www.ekmelic-music.org/en/extra/ekmelily.htm>`_. You can find pre-generated tuning files `here <https://github.com/levinericzimmermann/ekme-heji.ily>`_. **Example:** >>> from mutwo import music_parameters >>> from mutwo import abjad_converters >>> p = music_parameters.JustIntonationPitch('5/4') >>> converter_on_a = abjad_converters.MutwoPitchToHEJIAbjadPitch(reference_pitch='a') >>> converter_on_c = abjad_converters.MutwoPitchToHEJIAbjadPitch(reference_pitch='c') >>> converter_on_a.convert(my_ji_pitch) NamedPitch("csoaa''") >>> converter_on_c.convert(my_ji_pitch) NamedPitch("eoaa'") """ class _HEJIAccidental(object): """Fake abjad accidental Only for internal usage within the :class:`MutwoPitchToHEJIAbjadPitch`. """ def __init__(self, accidental: str): self._accidental = accidental def __str__(self) -> str: return self._accidental # necessary attributes, although they # won't be used at all semitones = 0 arrow = None def __init__( self, reference_pitch: str = "a", prime_to_heji_accidental_name: typing.Optional[dict[int, str]] = None, otonality_indicator: str = None, utonality_indicator: str = None, exponent_to_exponent_indicator: typing.Callable[[int], str] = None, tempered_pitch_indicator: str = None, ): # set default values if prime_to_heji_accidental_name is None: prime_to_heji_accidental_name = ( ekmelily_converters.configurations.DEFAULT_PRIME_TO_HEJI_ACCIDENTAL_NAME_DICT ) if otonality_indicator is None: otonality_indicator = ( ekmelily_converters.configurations.DEFAULT_OTONALITY_INDICATOR ) if utonality_indicator is None: utonality_indicator = ( ekmelily_converters.configurations.DEFAULT_UTONALITY_INDICATOR ) if exponent_to_exponent_indicator is None: exponent_to_exponent_indicator = ( ekmelily_converters.configurations.DEFAULT_EXPONENT_TO_EXPONENT_INDICATOR ) if tempered_pitch_indicator is None: tempered_pitch_indicator = ( ekmelily_converters.configurations.DEFAULT_TEMPERED_PITCH_INDICATOR ) self._reference_pitch = reference_pitch self._otonality_indicator = otonality_indicator self._utonality_indicator = utonality_indicator self._exponent_to_exponent_indicator = exponent_to_exponent_indicator self._tempered_pitch_indicator = tempered_pitch_indicator self._reference_index = ( music_parameters.constants.DIATONIC_PITCH_CLASS_CONTAINER[ reference_pitch ].index ) self._prime_to_heji_accidental_name = prime_to_heji_accidental_name self._mutwo_pitch_to_abjad_pitch = MutwoPitchToAbjadPitch( # When using HEJI we usually alter Lilypond to no longer support # quarter tones. Therefore we need to explicitly specify that # any WesternPitch is rounded to half tones. (fractions.Fraction(1, 1),) ) def _find_western_octave_for_just_intonation_pitch( self, pitch_to_convert: music_parameters.JustIntonationPitch, closest_pythagorean_pitch_name: str, ) -> int: octave = pitch_to_convert.octave + 4 closest_pythagorean_pitch_index = ( music_parameters.constants.DIATONIC_PITCH_CLASS_CONTAINER[ closest_pythagorean_pitch_name[0] ].index ) if closest_pythagorean_pitch_index < self._reference_index: octave += 1 pitch_as_western_pitch = music_parameters.WesternPitch( closest_pythagorean_pitch_name[0], octave ) reference_pitch_as_western_pitch = music_parameters.WesternPitch( self._reference_pitch, 4 ) expected_difference_in_cents = pitch_to_convert.interval while ( expected_difference_in_cents - ( ( pitch_as_western_pitch.midi_pitch_number - reference_pitch_as_western_pitch.midi_pitch_number ) * 100 ) > 300 ): pitch_as_western_pitch.octave += 1 while ( expected_difference_in_cents - ( ( pitch_as_western_pitch.midi_pitch_number - reference_pitch_as_western_pitch.midi_pitch_number ) * 100 ) < -300 ): pitch_as_western_pitch.octave -= 1 return pitch_as_western_pitch.octave def _find_heji_accidental_for_just_intonation_pitch( self, pitch_to_convert: music_parameters.JustIntonationPitch, abjad_pitch_class: abjad.NamedPitchClass, ): # find additional commas accidental_part_list = [str(abjad_pitch_class.accidental)] prime_to_exponent = ( pitch_to_convert.helmholtz_ellis_just_intonation_notation_commas.prime_to_exponent_dict ) for prime in sorted(prime_to_exponent.keys()): exponent = prime_to_exponent[prime] if exponent != 0: tonality = ( self._otonality_indicator if exponent > 0 else self._utonality_indicator ) heji_accidental_name = self._prime_to_heji_accidental_name[prime] exponent_indicator = self._exponent_to_exponent_indicator( abs(exponent) - 1 ) accidental_part_list.append( f"{tonality}{heji_accidental_name}{exponent_indicator}" ) accidental = self._HEJIAccidental("".join(accidental_part_list)) return accidental def _convert_just_intonation_pitch( self, pitch_to_convert: music_parameters.JustIntonationPitch, ) -> abjad.Pitch: # find pythagorean pitch closest_pythagorean_pitch_name = ( pitch_to_convert.get_closest_pythagorean_pitch_name(self._reference_pitch) ) abjad_pitch_class = abjad.NamedPitchClass(closest_pythagorean_pitch_name) accidental = self._find_heji_accidental_for_just_intonation_pitch( pitch_to_convert, abjad_pitch_class ) abjad_pitch_class._accidental = accidental octave = self._find_western_octave_for_just_intonation_pitch( pitch_to_convert, closest_pythagorean_pitch_name ) abjad_pitch = abjad.NamedPitch(octave=octave) abjad_pitch._pitch_class = abjad_pitch_class return abjad_pitch
[docs] def convert(self, pitch_to_convert: music_parameters.abc.Pitch) -> abjad.Pitch: if isinstance(pitch_to_convert, music_parameters.JustIntonationPitch): abjad_pitch = self._convert_just_intonation_pitch(pitch_to_convert) else: abjad_pitch = self._mutwo_pitch_to_abjad_pitch.convert(pitch_to_convert) return abjad_pitch