Skip to content

Commit

Permalink
parameters/Duration: Refactor inner repr to float instead of fraction
Browse files Browse the repository at this point in the history
In d9a861c the explicit duration
type was introduced. This commit also changed the internal
representation of durations from floats to fractions. The main reason
for this change was to avoid floating point errors when comparing two
different durations (and because in music it's more 'clean' to think of
ratios than of floats). Unfortunately this change had some serious
problems: it made the performance of the system very bad as calculating
with fractions is much slower than calculating with floats. Due to this
very often the durations were internally converted to floats before
making calculations with them (see for instance fc49bb7).

Due to these problems it became clear that it's wiser to use float as a
durations value type. The floating point errors are mostly solved by rounding
the numbers. This change can only be done now in version 2.0.0, because
it breaks backwards compatibility. It has some additional implications:

- the 'Duration.duration_in_floats' attribute is dropped, as
  'Duration.duration' is already always in floats

- a new class 'RatioDuration' was introduced which uses a fraction
  for internal duration representation. This class is used if the
  user passes a ratio string to the duration parser (e.g. something
  like '3/2' or '4/1' is parsed to 'RatioDuration')
  • Loading branch information
levinericzimmermann committed Nov 21, 2023
1 parent 0f4f792 commit 90b0a3f
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 110 deletions.
10 changes: 5 additions & 5 deletions mutwo/core_converters/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ class EventConverter(Converter):
... ]
... )
>>> DurationPrintConverter().convert(random_event)
DirectDuration(duration = 0): DirectDuration(duration = 332813340356277/281474976710656)
DirectDuration(duration = 332813340356277/281474976710656): DirectDuration(duration = 3729376151804513/2251799813685248)
DirectDuration(duration = 6391882874654729/2251799813685248): DirectDuration(duration = 7017823472572815/4503599627370496)
DirectDuration(duration = 19801589221882273/4503599627370496): DirectDuration(duration = 449779690686865/281474976710656)
DirectDuration(duration = 26998064272872113/4503599627370496): DirectDuration(duration = 5180362984867255/4503599627370496)
DirectDuration(duration = 0.0): DirectDuration(duration = 1.1823905068)
DirectDuration(duration = 1.1823905068): DirectDuration(duration = 1.6561757085)
DirectDuration(duration = 2.8385662153): DirectDuration(duration = 1.5582698404)
DirectDuration(duration = 4.3968360557): DirectDuration(duration = 1.5979384595)
DirectDuration(duration = 5.9947745152): DirectDuration(duration = 1.1502716523)
"""

@abc.abstractmethod
Expand Down
8 changes: 4 additions & 4 deletions mutwo/core_converters/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,12 @@ def convert(self, simple_event_to_convert: core_events.SimpleEvent) -> typing.An
>>> from mutwo import core_converters
>>> from mutwo import core_events
>>> simple_event = core_events.SimpleEvent(duration=10)
>>> simple_event = core_events.SimpleEvent(duration=10.0)
>>> simple_event_to_duration = core_converters.SimpleEventToAttribute(
... 'duration', 0
... )
>>> simple_event_to_duration.convert(simple_event)
DirectDuration(10)
DirectDuration(10.0)
>>> simple_event_to_pasta = core_converters.SimpleEventToAttribute(
... 'pasta', 'spaghetti'
... )
Expand Down Expand Up @@ -90,9 +90,9 @@ class MutwoParameterDictToKeywordArgument(core_converters.abc.Converter):
>>> from mutwo import core_parameters
>>> mutwo_parameter_dict_to_keyword_argument = core_converters.MutwoParameterDictToKeywordArgument('duration')
>>> mutwo_parameter_dict_to_keyword_argument.convert(
... {'duration': core_parameters.DirectDuration(1)}
... {'duration': core_parameters.DirectDuration(1.0)}
... )
('duration', DirectDuration(1))
('duration', DirectDuration(1.0))
"""

def __init__(
Expand Down
2 changes: 1 addition & 1 deletion mutwo/core_converters/tempos.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def convert(self, event_to_convert: core_events.abc.Event) -> core_events.abc.Ev
>>> my_tempo_converter = core_converters.TempoConverter(tempo_envelope)
>>> my_events = core_events.SequentialEvent([core_events.SimpleEvent(d) for d in (3, 2, 5)])
>>> my_tempo_converter.convert(my_events)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 3)), SimpleEvent(duration = DirectDuration(duration = 3602879701896397/1125899906842624)), SimpleEvent(duration = DirectDuration(duration = 6))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 3.0)), SimpleEvent(duration = DirectDuration(duration = 3.2)), SimpleEvent(duration = DirectDuration(duration = 6.0))])
"""
copied_event_to_convert = event_to_convert.destructive_copy()
self._convert_event(copied_event_to_convert, core_parameters.DirectDuration(0))
Expand Down
54 changes: 30 additions & 24 deletions mutwo/core_events/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ def set(self, attribute_name: str, value: typing.Any) -> Event:
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(2)])
>>> sequential_event.set('duration', 10).set('my_new_attribute', 'hello-world!')
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 10))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 10.0))])
"""
setattr(self, attribute_name, value)
return self
Expand Down Expand Up @@ -283,10 +283,10 @@ def get_parameter(
... [core_events.SimpleEvent(2), core_events.SimpleEvent(3)]
... )
>>> sequential_event.get_parameter('duration')
(DirectDuration(2), DirectDuration(3))
(DirectDuration(2.0), DirectDuration(3.0))
>>> simple_event = core_events.SimpleEvent(10)
>>> simple_event.get_parameter('duration')
DirectDuration(10)
DirectDuration(10.0)
>>> simple_event.get_parameter('undefined_parameter')
"""

Expand Down Expand Up @@ -318,9 +318,9 @@ def set_parameter(
... [core_events.SimpleEvent(2), core_events.SimpleEvent(3)]
... )
>>> sequential_event.set_parameter('duration', lambda duration: duration * 2)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 4)), SimpleEvent(duration = DirectDuration(duration = 6))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 4.0)), SimpleEvent(duration = DirectDuration(duration = 6.0))])
>>> sequential_event.get_parameter('duration')
(DirectDuration(4), DirectDuration(6))
(DirectDuration(4.0), DirectDuration(6.0))
**Warning:**
Expand Down Expand Up @@ -369,10 +369,10 @@ def mutate_parameter(
>>> sequential_event.mutate_parameter(
... 'duration', lambda duration: duration.add(1)
... )
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2.0))])
>>> # now duration should be + 1
>>> sequential_event.get_parameter('duration')
(DirectDuration(2),)
(DirectDuration(2.0),)
**Warning:**
Expand Down Expand Up @@ -402,11 +402,11 @@ def reset_tempo_envelope(self) -> Event:
>>> simple_event = core_events.SimpleEvent(duration = 1)
>>> simple_event.tempo_envelope[0].value = 100
>>> simple_event.tempo_envelope
TempoEnvelope([TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 1), tempo_point = DirectTempoPoint(BPM = 60, reference = 1), value = 100), TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1))])
TempoEnvelope([TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 1.0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1), value = 100), TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 0.0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1))])
>>> simple_event.reset_tempo_envelope()
SimpleEvent(duration = DirectDuration(duration = 1))
SimpleEvent(duration = DirectDuration(duration = 1.0))
>>> simple_event.tempo_envelope
TempoEnvelope([TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 1), tempo_point = DirectTempoPoint(BPM = 60, reference = 1)), TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1))])
TempoEnvelope([TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 1.0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1)), TempoEvent(curve_shape = 0, duration = DirectDuration(duration = 0.0), tempo_point = DirectTempoPoint(BPM = 60, reference = 1))])
"""
self.tempo_envelope = core_events.TempoEnvelope([[0, 60], [1, 60]])
return self
Expand Down Expand Up @@ -446,7 +446,7 @@ def cut_out(
... [core_events.SimpleEvent(3), core_events.SimpleEvent(2)]
... )
>>> sequential_event.cut_out(1, 4)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2)), SimpleEvent(duration = DirectDuration(duration = 1))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2.0)), SimpleEvent(duration = DirectDuration(duration = 1.0))])
"""

@abc.abstractmethod
Expand All @@ -467,7 +467,7 @@ def cut_off(
... [core_events.SimpleEvent(3), core_events.SimpleEvent(2)]
... )
>>> sequential_event.cut_off(1, 3)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 2))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 2.0))])
"""

def split_at(
Expand Down Expand Up @@ -500,9 +500,9 @@ def split_at(
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(3)])
>>> sequential_event.split_at(1)
(SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1))]), SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2))]))
(SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0))]), SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 2.0))]))
>>> sequential_event[0].split_at(1)
(SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 2)))
(SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 2.0)))
"""
if not absolute_time:
raise core_utilities.NoSplitTimeError()
Expand Down Expand Up @@ -702,12 +702,18 @@ def duration(self, duration: core_parameters.abc.Duration):
if not self: # If empty and duration == 0, we'd run into ZeroDivision
raise core_utilities.CannotSetDurationOfEmptyComplexEvent()

duration = core_events.configurations.UNKNOWN_OBJECT_TO_DURATION(duration)
if (old_duration := self.duration) != 0:
duration_in_floats = core_events.configurations.UNKNOWN_OBJECT_TO_DURATION(
duration
).duration
if (old_duration_in_floats := self.duration.duration) != 0:

def f(event_duration: core_parameters.abc.Duration):
return core_utilities.scale(
event_duration, 0, old_duration, 0, duration
event_duration.duration,
0,
old_duration_in_floats,
0,
duration_in_floats,
)

else:
Expand Down Expand Up @@ -864,10 +870,10 @@ def get_event_from_index_sequence(
... [core_events.SequentialEvent([core_events.SimpleEvent(2)])]
... )
>>> nested_sequential_event.get_event_from_index_sequence((0, 0))
SimpleEvent(duration = DirectDuration(duration = 2))
SimpleEvent(duration = DirectDuration(duration = 2.0))
>>> # this is equal to:
>>> nested_sequential_event[0][0]
SimpleEvent(duration = DirectDuration(duration = 2))
SimpleEvent(duration = DirectDuration(duration = 2.0))
"""

return core_utilities.get_nested_item_from_index_sequence(index_sequence, self)
Expand Down Expand Up @@ -921,7 +927,7 @@ def remove_by( # type: ignore
... [core_events.SimpleEvent(1), core_events.SimpleEvent(3), core_events.SimpleEvent(2)]
... )
>>> simultaneous_event.remove_by(lambda event: event.duration > 2)
SimultaneousEvent([SimpleEvent(duration = DirectDuration(duration = 3))])
SimultaneousEvent([SimpleEvent(duration = DirectDuration(duration = 3.0))])
"""

for item_index, item in zip(reversed(range(len(self))), reversed(self)):
Expand Down Expand Up @@ -1030,7 +1036,7 @@ def squash_in(
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(3)])
>>> sequential_event.squash_in(1, core_events.SimpleEvent(1.5))
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 3/2)), SimpleEvent(duration = DirectDuration(duration = 1/2))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 1.5)), SimpleEvent(duration = DirectDuration(duration = 0.5))])
"""

@abc.abstractmethod
Expand All @@ -1053,7 +1059,7 @@ def slide_in(
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(3)])
>>> sequential_event.slide_in(1, core_events.SimpleEvent(1.5))
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 3/2)), SimpleEvent(duration = DirectDuration(duration = 2))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 1.5)), SimpleEvent(duration = DirectDuration(duration = 2.0))])
"""

@abc.abstractmethod
Expand All @@ -1069,7 +1075,7 @@ def split_child_at(
>>> from mutwo import core_events
>>> sequential_event = core_events.SequentialEvent([core_events.SimpleEvent(3)])
>>> sequential_event.split_child_at(1)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 2))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 2.0))])
"""

@abc.abstractmethod
Expand Down Expand Up @@ -1108,5 +1114,5 @@ def extend_until(
>>> from mutwo import core_events
>>> s = core_events.SequentialEvent([core_events.SimpleEvent(1)])
>>> s.extend_until(10)
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1)), SimpleEvent(duration = DirectDuration(duration = 9))])
SequentialEvent([SimpleEvent(duration = DirectDuration(duration = 1.0)), SimpleEvent(duration = DirectDuration(duration = 9.0))])
"""
Loading

0 comments on commit 90b0a3f

Please sign in to comment.