Skip to content

Commit

Permalink
events: Add ComplexEvent.slide_in method
Browse files Browse the repository at this point in the history
The new method 'slide_in' functions as a complementary partner to the
already defined 'ComplexEvent.squash_in'. It allows to insert new
events without making any existing event shorter. It's actually simpler
than 'squash_in' which doesn't prolong the event into which the new
event is squashed in.

'ComplexEvent.slide_in' is very handy and useful if one wants to prolong
a specific 'ComplexEvent' by a certain duration without time stretch /
changing the tempo.
  • Loading branch information
levinericzimmermann committed Nov 26, 2022
1 parent 131f5b3 commit f145c3f
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 7 deletions.
33 changes: 31 additions & 2 deletions mutwo/core_events/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,7 +878,7 @@ def metrize(self, mutate: bool = True) -> ComplexEvent:
def squash_in(
self, start: core_parameters.abc.Duration, event_to_squash_in: Event
) -> typing.Optional[ComplexEvent[T]]:
"""Time-based insert of a new event into the present event.
"""Time-based insert of a new event with overriding given event.
:param start: Absolute time where the event shall be inserted.
:param event_to_squash_in: the event that shall be squashed into
Expand All @@ -887,7 +887,9 @@ def squash_in(
If set to ``True`` the object itself will be changed and the function will
return the changed object. Default to ``True``.
Squash in a new event to the present event.
Unlike `ComplexEvent.slide_in` the events duration won't change.
If there is already an event at `start` this event will be shortened
or removed.
**Example:**
Expand All @@ -898,6 +900,33 @@ def squash_in(
SequentialEvent([SimpleEvent(duration = 1), SimpleEvent(duration = 1.5), SimpleEvent(duration = 0.5)])
"""

@abc.abstractmethod
def slide_in(
self, start: core_parameters.abc.Duration, event_to_slide_in: Event
) -> ComplexEvent[T]:
"""Time-based insert of a new event into the present event.
:param start: Absolute time where the event shall be inserted.
:param event_to_slide_in: the event that shall be slide into
the present event.
:param mutate: If ``False`` the function will return a copy of the given object.
If set to ``True`` the object itself will be changed and the function will
return the changed object. Default to ``True``.
Unlike `ComplexEvent.squash_in` the events duration will be prolonged
by the event which is added. If there is an event at `start` the
event will be split into two parts, but it won't be shortened or
processed in any other way.
**Example:**
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(3)])
>>> sequential_event.slide_in(1, core_events.SimpleEvent(1.5))
>>> print(sequential_event)
SequentialEvent([SimpleEvent(duration = 1), SimpleEvent(duration = 1.5), SimpleEvent(duration = 2)])
"""

@abc.abstractmethod
def split_child_at(
self, absolute_time: core_parameters.abc.Duration
Expand Down
28 changes: 28 additions & 0 deletions mutwo/core_events/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,19 @@ def squash_in( # type: ignore

self.insert(insert_index, event_to_squash_in)

@core_utilities.add_copy_option
def slide_in(
self,
start: core_parameters.abc.Duration,
event_to_slide_in: core_events.abc.Event,
) -> SequentialEvent[T]:
start = core_events.configurations.UNKNOWN_OBJECT_TO_DURATION(start)
start_in_floats = start.duration_in_floats
self._assert_start_in_range(start_in_floats)
self[:], b = self.split_at(start)
self.extend([event_to_slide_in] + b)
return self

@core_utilities.add_copy_option
def split_child_at(
self, absolute_time: core_parameters.abc.Duration | typing.Any
Expand Down Expand Up @@ -751,6 +764,21 @@ def squash_in( # type: ignore
except AttributeError:
raise core_utilities.ImpossibleToSquashInError(self, event_to_squash_in)

@core_utilities.add_copy_option
def slide_in(
self,
start: core_parameters.abc.Duration,
event_to_slide_in: core_events.abc.Event,
) -> SimultaneousEvent[T]:
start = core_events.configurations.UNKNOWN_OBJECT_TO_DURATION(start)
self._assert_start_in_range(start)
for event in self:
try:
event.slide_in(start, event_to_slide_in) # type: ignore
# Simple events don't have a 'slide_in' method.
except AttributeError:
raise core_utilities.ImpossibleToSlideInError(self, event_to_slide_in)

@core_utilities.add_copy_option
def split_child_at(
self, absolute_time: core_constants.DurationType
Expand Down
20 changes: 16 additions & 4 deletions mutwo/core_utilities/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"InvalidStartValueError",
"InvalidPointError",
"ImpossibleToSquashInError",
"ImpossibleToSlideInError",
"InvalidStartAndEndValueError",
"InvalidCutOutStartAndEndValuesError",
"SplitUnavailableChildError",
Expand Down Expand Up @@ -58,17 +59,28 @@ def __init__(self, point, point_count):
)


class ImpossibleToSquashInError(TypeError):
def __init__(self, event_to_be_squashed_into, event_to_squash_in):
class ImpossibleToPutInError(TypeError):
def __init__(self, event_to_be_put_into, event_to_put_in, method_name):
m = method_name
super().__init__(
f"Can't squash '{event_to_squash_in}' in '{event_to_be_squashed_into}'. "
f"Can't {m} '{event_to_put_in}' in '{event_to_be_put_into}'. "
"Does the SimultaneousEvent contain SimpleEvents or events that inherit"
" from SimpleEvent? For being able to squash in, the"
f" from SimpleEvent? For being able to {m} in, the"
" SimultaneousEvent needs to only contain SequentialEvents or"
" SimultaneousEvents."
)


class ImpossibleToSquashInError(ImpossibleToPutInError):
def __init__(self, event_to_be_squashed_into, event_to_squash_in):
super().__init__(event_to_be_squashed_into, event_to_squash_in, "squash")


class ImpossibleToSlideInError(TypeError):
def __init__(self, event_to_be_slided_into, event_to_slide_in):
super().__init__(event_to_be_slided_into, event_to_slide_in, "slide")


class InvalidStartAndEndValueError(Exception):
def __init__(self, start, end):
super().__init__(
Expand Down
61 changes: 60 additions & 1 deletion tests/events/basic_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def test_setitem_index(self):
def test_setitem_tag(self):
simple_event = core_events.SimpleEvent(100).set("unique-id", 100)
tag0, tag1, tag2 = self.tag_sequence()
self.sequence[tag1] = simple_event.set('tag', tag1)
self.sequence[tag1] = simple_event.set("tag", tag1)
self.assertEqual(self.sequence[tag1], simple_event)

def test_duration(self):
Expand Down Expand Up @@ -502,6 +502,37 @@ def test_squash_in_event_with_0_duration(self):
squashed_in_sequence.squash_in(1, core_events.SimpleEvent(0).set("test", 100))
self.assertEqual(squashed_in_sequence[1].get_parameter("test"), 100)

def test_slide_in(self):
s, se = core_events.SimpleEvent, core_events.SequentialEvent
f = fractions.Fraction

for start, event_to_slide_in, expected_sequential_event in (
(0, s(100), se([s(100), s(1), s(2), s(3)])),
(1, s(100), se([s(1), s(100), s(2), s(3)])),
(2, s(100), se([s(1), s(1), s(100), s(1), s(3)])),
(3, s(100), se([s(1), s(2), s(100), s(3)])),
(4, s(100), se([s(1), s(2), s(1), s(100), s(2)])),
(
f(6e-10),
s(100),
se([s(f(6e-10)), s(100), s(1 - f(6e-10)), s(2), s(3)]),
),
):
with self.subTest(start=start):
self.assertEqual(
self.sequence.slide_in(start, event_to_slide_in, mutate=False),
expected_sequential_event,
)

def test_slide_in_with_invalid_start(self):
s = core_events.SimpleEvent(1)
self.assertRaises(
core_utilities.InvalidStartAndEndValueError, self.sequence.slide_in, -1, s
)
self.assertRaises(
core_utilities.InvalidStartValueError, self.sequence.slide_in, 100, s
)

def test_tie_by(self):
# Ensure empty event can be tied without error
self.assertEqual(
Expand Down Expand Up @@ -915,6 +946,34 @@ def test_squash_in(self):
expected_simultaneous_event,
)

def test_slide_in(self):
s, si, se = (
core_events.SimpleEvent,
core_events.SimultaneousEvent,
core_events.SequentialEvent,
)

for start, event_to_slide_in, expected_simultaneous_event in (
(0, s(100), si([se([s(100), s(1), s(2), s(3)])] * 2)),
(1, s(100), si([se([s(1), s(100), s(2), s(3)])] * 2)),
(2, s(100), si([se([s(1), s(1), s(100), s(1), s(3)])] * 2)),
):
with self.subTest(start=start):
self.assertEqual(
self.nested_sequence.slide_in(
start, event_to_slide_in, mutate=False
),
expected_simultaneous_event,
)

def test_slide_in_exception(self):
self.assertRaises(
core_utilities.ImpossibleToSlideInError,
self.sequence.slide_in,
0,
core_events.SimpleEvent(1),
)

def test_split_child_at(self):
simultaneous_event0 = core_events.SimultaneousEvent(
[core_events.SequentialEvent([core_events.SimpleEvent(3)])]
Expand Down

0 comments on commit f145c3f

Please sign in to comment.