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