"""Algorithms which are related to US-American tuning theorist Erv Wilson."""
import functools
import operator
import itertools
import typing
from mutwo import music_parameters
try:
from mutwo import common_generators
BRUN_FOUND = True
except ImportError:
BRUN_FOUND = False
__all__ = (
"make_product_pitch",
"make_common_product_set_scale",
"make_wilsons_brun_euclidean_algorithm_generator",
)
[docs]def make_product_pitch(
number_sequence: typing.Sequence[int],
tonality: bool,
normalize: bool = False,
) -> music_parameters.JustIntonationPitch:
"""Make :class:`~mutwo.core.parameters.music_parameters.JustIntonationPitch` from the product of one, two or more number_sequence.
:param number_sequence: The number which shall be multiplied to make a new pitch.
:type number_sequence: typing.Sequence[int]
:param tonality: ``True`` for putting the resulting product to the numerator of the
frequency ratio and ``False`` for putting the resulting product to the
denominator.
:type tonality: bool
:param normalize: ``True`` to normalize the new pitch to the middle octave.
Default to ``False``.
:type normalize: bool, optional
"""
product = functools.reduce(operator.mul, number_sequence)
if tonality:
ratio = f"{product}/1"
else:
ratio = f"1/{product}"
pitch = music_parameters.JustIntonationPitch(ratio)
if normalize:
pitch.normalize()
return pitch
[docs]def make_common_product_set_scale(
number_sequence: typing.Sequence[int],
n_combinations: int,
tonality: bool,
normalize: bool = False,
) -> tuple[music_parameters.JustIntonationPitch, ...]:
"""Make common product set scale as described in Wilsons letter to Fokker.
:param number_sequence: The number_sequence which will be combined to single music_parameters.
:type number_sequence: typing.Sequence[int]
:param n_combinations: How many number_sequence will be combined for each pitch.
:type n_combinations: int
:param tonality: ``True`` for otonality and ``False`` for utonality.
:type tonality: bool
:param normalize: ``True`` if music_parameters.shall become normalized to the same octave.
:type normalize: bool
**Example:**
>>> from mutwo.generators import wilson
>>> wilson.make_common_product_set_scale((3, 5, 7, 9), 2, True)
(JustIntonationPitch(15),
JustIntonationPitch(21),
JustIntonationPitch(27),
JustIntonationPitch(35),
JustIntonationPitch(45),
JustIntonationPitch(63))
>>> wilson.make_common_product_set_scale((3, 5, 7, 9), 2, False)
(JustIntonationPitch(1/15),
JustIntonationPitch(1/21),
JustIntonationPitch(1/27),
JustIntonationPitch(1/35),
JustIntonationPitch(1/45),
JustIntonationPitch(1/63))
"""
common_product_set_scale = []
for combined_number_sequence in itertools.combinations(
number_sequence, n_combinations
):
common_product_set_scale.append(
make_product_pitch(combined_number_sequence, tonality, normalize)
)
return tuple(common_product_set_scale)
[docs]def make_wilsons_brun_euclidean_algorithm_generator(
pitch_tuple: tuple[
music_parameters.JustIntonationPitch,
music_parameters.JustIntonationPitch,
music_parameters.JustIntonationPitch,
],
subtraction_index: typing.Literal[1, 2] = 1,
direction_forward: bool = True,
direction_reverse: bool = False,
) -> typing.Generator:
"""Make constant structure scale with Wilsons adaption of Bruns euclidean algorithm.
:param pitch_tuple: The initial seed composed of three individual music_parameters. The
biggest pitch will be the period of the repeating scale, therefore it is
recommended to use ``music_parameters.JustIntonationPitch("2/1")`` here (if one
desires an octave repeating scale).
:type pitch_tuple: tuple[music_parameters.JustIntonationPitch, music_parameters.JustIntonationPitch, music_parameters.JustIntonationPitch],
:param subtraction_index: Set to 1 if the largest interval should be subtracted by
the second interval. Set to 2 if the largest interval should be subtracted by
the smallest interval.
:type subtraction_index: int
:param direction_forward: Set to ``True`` if the algorithm should include the
normal sorted replacement of an interval. Default to ``True``.
:type direction_forward: bool
:param direction_reverse: Set to ``True`` if the algorithm should include the
reversed replacement of an interval. Default to ``False``.
:type direction_reverse: bool
:return: Generator which returns a list of intervals. Accumulate the intervals from
``music_parameters.JustIntonationPitch("1/1")`` to get the scale music_parameters.
**Example:**
>>> from mutwo.ext.parameters import pitches
>>> from mutwo.ext.generators import wilson
>>> wilsons_brun_euclidean_algorithm_generator = (
>>> wilson.make_wilsons_brun_euclidean_algorithm_generator(
>>> (
>>> music_parameters.JustIntonationPitch("2/1"),
>>> music_parameters.JustIntonationPitch("3/2"),
>>> music_parameters.JustIntonationPitch("5/4"),
>>> )
>>> )
>>> )
>>> next(wilsons_brun_euclidean_algorithm_generator)
((JustIntonationPitch(2),),)
>>> next(wilsons_brun_euclidean_algorithm_generator)
((JustIntonationPitch(3/2), JustIntonationPitch(4/3)),)
>>> next(wilsons_brun_euclidean_algorithm_generator)
((JustIntonationPitch(4/3), JustIntonationPitch(9/8), JustIntonationPitch(4/3)),)
"""
if not BRUN_FOUND:
raise ImportError(
"Generators module 'brun' couldn't be found. "
"This is most likely because you didn't install "
"mutwo.ext-common-generators yet."
)
try:
assert direction_forward or direction_reverse
except AssertionError:
raise ValueError("Can't set both directions to False!")
def fetch_first_and_second_interval(
brun_interval_tuple: tuple[
music_parameters.JustIntonationPitch,
music_parameters.JustIntonationPitch,
music_parameters.JustIntonationPitch,
]
) -> tuple[
music_parameters.JustIntonationPitch, music_parameters.JustIntonationPitch
]:
return brun_interval_tuple[0], brun_interval_tuple[subtraction_index]
bruns_euclidean_algorithm_generator = (
common_generators.make_bruns_euclidean_algorithm_generator(
pitch_tuple,
subtraction_index=subtraction_index,
)
)
brun_interval_tuple = next(bruns_euclidean_algorithm_generator)[0]
previous_interval, previous_second_interval = fetch_first_and_second_interval(
brun_interval_tuple
)
interval_list_list = [[previous_interval]]
previous_interval_tuple_tuple = tuple([])
while True:
new_interval_tuple_tuple = tuple(map(tuple, interval_list_list))
if new_interval_tuple_tuple != previous_interval_tuple_tuple:
yield new_interval_tuple_tuple
previous_interval_tuple_tuple = new_interval_tuple_tuple
brun_interval_tuple, _, previous_subtraction_result = next(
bruns_euclidean_algorithm_generator
)
interval_replacement_tuple_list = []
if direction_forward:
interval_replacement_tuple_list.append(
(
previous_second_interval,
previous_subtraction_result,
)
)
if direction_reverse:
interval_replacement_tuple_list.append(
(
previous_subtraction_result,
previous_second_interval,
)
)
new_interval_list_list = []
for interval_list in interval_list_list:
for interval_replacement_tuple in interval_replacement_tuple_list:
new_interval_list = list(interval_list)
for index, interval in enumerate(new_interval_list):
if interval == previous_interval:
new_interval_list[
index : index + 1
] = interval_replacement_tuple
new_interval_list_list.append(new_interval_list)
interval_list_list = new_interval_list_list
previous_interval, previous_second_interval = fetch_first_and_second_interval(
brun_interval_tuple
)