Skip to content

Commit

Permalink
events/Envelope += time_range_to_point_tuple
Browse files Browse the repository at this point in the history
This new function is useful if one wants to do maths with control points
over a given range - e.g. for manually calculating the integral in a
given time range. Because we want to do this in the next commit, this
patch prepares the Envelope class to already support finding all control
points.
  • Loading branch information
levinericzimmermann committed Nov 7, 2023
1 parent 8ae9f40 commit f42c5fa
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 1 deletion.
56 changes: 55 additions & 1 deletion mutwo/core_events/envelopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
from __future__ import annotations

import bisect
import functools
import typing

from scipy import integrate
import ranges

from mutwo import core_constants
from mutwo import core_events
Expand Down Expand Up @@ -405,7 +407,11 @@ def _point_at(

else:
e = self[absolute_time_tuple.index(absolute_time)]
point = (absolute_time, self._event_to_value(e), self.event_to_curve_shape(e))
point = (
absolute_time,
self._event_to_value(e),
self.event_to_curve_shape(e),
)

return point

Expand Down Expand Up @@ -584,6 +590,54 @@ def find_duration(

return self

def time_range_to_point_tuple(
self, time_range: ranges.Range
) -> tuple[CompletePoint, ...]:
"""Return all control points in given time range.
:param time_range: Start and end time encapsulated in a
:class:`ranges.Range` object.
:type time_range: ranges.Range
If at start and end time aren't any control points, the functions
creates them ad-hoc via ``point_at``.
"""

start, end = (
core_events.configurations.UNKNOWN_OBJECT_TO_DURATION(o)
for o in (time_range.start, time_range.end)
)
absolute_time_tuple, duration = self._absolute_time_tuple_and_duration

p = functools.partial( # point_at
self._point_at, absolute_time_tuple=absolute_time_tuple, duration=duration
)

point_list = []

if start not in absolute_time_tuple:
point_list.append(p(start))
i0 = bisect.bisect_left(absolute_time_tuple, start)
else:
i0 = absolute_time_tuple.index(start)

if end not in absolute_time_tuple:
i1 = bisect.bisect_left(absolute_time_tuple, end)
last_point = p(end)
else:
i1 = absolute_time_tuple.index(end) + 1
last_point = None

for t, ev in zip(absolute_time_tuple[i0:i1], self[i0:i1]):
point_list.append(
(t, self._event_to_value(ev), self.event_to_curve_shape(ev))
)

if last_point is not None:
point_list.append(last_point)

return tuple(point_list)

def integrate_interval(
self, start: core_constants.DurationType, end: core_constants.DurationType
) -> float:
Expand Down
32 changes: 32 additions & 0 deletions tests/events/envelopes_tests.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import typing
import unittest

import ranges

from mutwo import core_constants
from mutwo import core_events
from mutwo import core_parameters
Expand Down Expand Up @@ -159,6 +161,36 @@ def test_point_at_points(self):
self.assertEqual(self.envelope.point_at(1), (d(1), 1, 1))
self.assertEqual(self.envelope.point_at(2), (d(2), 0, -1))

def test_time_range_to_point_tuple(self):
d = core_parameters.DirectDuration
# All pre-defined points
self.assertEqual(
self.envelope.time_range_to_point_tuple(ranges.Range(0, 5)),
(
(d(0), 0, 0),
(d(1), 1, 1),
(d(2), 0, -1),
(d(3), 1, 0),
(d(5), 0.5, 0),
),
)
# Interpolation points
# first point
self.assertEqual(
self.envelope.time_range_to_point_tuple(ranges.Range(0.5, 1)),
((d(0.5), 0.5, 0), (d(1), 1, 1)),
)
# last point
self.assertEqual(
self.envelope.time_range_to_point_tuple(ranges.Range(0, 0.5)),
((d(0), 0, 0), (d(0.5), 0.5, 0)),
)
# both points
self.assertEqual(
self.envelope.time_range_to_point_tuple(ranges.Range(0.25, 0.75)),
((d(0.25), 0.25, 0), (d(0.75), 0.75, 0)),
)

def test_from_points_simple(self):
envelope_from_init = core_events.Envelope(
[self.EnvelopeEvent(1, 0, 10), self.EnvelopeEvent(0, 1)]
Expand Down

0 comments on commit f42c5fa

Please sign in to comment.