Source code for mutwo.common_generators.koenig
"""Algorithms which are related to German-Dutch composer G. M. Koenig."""
import numpy as np # type: ignore
import ranges # type: ignore
from mutwo import common_utilities
from mutwo import core_events
__all__ = ("Tendency",)
[docs]class Tendency(object):
"""Tendency offers an interface for dynamically changing minima / maxima areas.
:param minima_curve: The curve which describes the smallest allowed value over the
time axis.
:type minima_curve: core_events.Envelope
:param maxima_curve: The curve which describes the biggest allowed value over the
time axis.
:type maxima_curve: core_events.Envelope
:param random_seed: The random seed which shall be set.
:type random_seed: int
The class is based on Gottfried Michael Koenigs algorithm of "Tendenz-Masken" in
his program "Projekt 2" where those minima / maxima areas represent probability
fields.
**Example:**
>>> import core_events
>>> from mutwo.generators import koenig
>>> minima_curve = core_events.Envelope.from_points((0, 0), (1, 1), (2, 0))
>>> maxima_curve = core_events.Envelope.from_points((0, 1), (1, 2), (2, 3))
>>> my_tendency = koenig.Tendency(minima_curve, maxima_curve)
>>> my_tendency.value_at(0.5)
0.6456692551041303
>>> my_tendency.value_at(0.5)
0.9549270045140213
"""
def __init__(
self,
minima_curve: core_events.Envelope,
maxima_curve: core_events.Envelope,
random_seed: int = 100,
):
self._assert_curves_are_valid(minima_curve, maxima_curve)
self._minima_curve = minima_curve
self._maxima_curve = maxima_curve
self._random = np.random.default_rng(random_seed)
def __repr__(self) -> str:
return f"{type(self).__name__}({self.minima_curve}, {self.maxima_curve})"
@staticmethod
def _assert_curves_are_valid(
minima_curve: core_events.Envelope, maxima_curve: core_events.Envelope
):
"""Helper method that asserts the curves are a valid min/max pair."""
# make sure both curves have equal duration
if minima_curve.duration != maxima_curve.duration:
raise common_utilities.UnequalEnvelopeDurationError(
minima_curve, maxima_curve
)
# It would be better if we could compare all local extrema.
# But there is no public function in core_events.Envelope yet.
# Even if there would be a function to find local extrema it
# is uncertain if this would be faster.
# We want to make sure that at any time point minima_curve
# < maxima_curve.
point_to_compare_tuple = (
minima_curve.absolute_time_tuple
+ maxima_curve.absolute_time_tuple
+ (0, minima_curve.duration)
)
for time in point_to_compare_tuple:
if not minima_curve.value_at(time) < maxima_curve.value_at(time):
raise common_utilities.InvalidMinimaCurveAndMaximaCurveCombination(
f"At time '{time}' 'minima_curve' isn't smaller "
"than 'maxima_curve'!"
)
@property
def minima_curve(self) -> core_events.Envelope:
return self._minima_curve
@minima_curve.setter
def minima_curve(self, minima_curve: core_events.Envelope) -> core_events.Envelope:
self._assert_curves_are_valid(minima_curve, self.maxima_curve)
self._minima_curve = minima_curve
@property
def maxima_curve(self) -> core_events.Envelope:
return self._maxima_curve
@maxima_curve.setter
def maxima_curve(self, maxima_curve: core_events.Envelope) -> core_events.Envelope:
self._assert_curves_are_valid(self.minima_curve, maxima_curve)
self._maxima_curve = maxima_curve
[docs] def range_at(self, time: float) -> ranges.Range:
"""Get minima / maxima range at requested time."""
return ranges.Range(
self.minima_curve.value_at(time), self.maxima_curve.value_at(time)
)
[docs] def value_at(self, time: float) -> float:
"""Get value at requested time."""
range_at_time = self.range_at(time)
return self._random.uniform(range_at_time.start, range_at_time.end)