Skip to content

Commit

Permalink
Refactor parameter.$PROPERTY to parameter.$UNIT
Browse files Browse the repository at this point in the history
Before this patch parameters, which are identified
by only one property (parameters that inherit from
'abc.SingleValueParameter'), had the naming convention
'the pivotal property must be named by itself'.
So for instance the pivotal property 'frequency' of the
parameter 'Pitch' was named 'frequency'.

The following is a list of single-value parameters in
'mutwo.core' and 'mutwo.music' as it can be found before
this patch:

    Pitch.frequency
    Volume.amplitude
    Duration.duration
    TempoPoint.tempo
    PitchInterval.interval
    Lyric.phonetic_representation

While some of these names are ok (frequency or amplitude),
some other names look more stupid and confusing (the duration
of a duration, the interval of a pitch interval). Perhaps due
to this an earlier version of 'PitchInterval' used the property
'cents', before it was refactored to 'interval' in mutwo-org/mutwo.music@e3372c7
to obey to the general naming convention. The idea of this patch
is instead of renaming 'PitchInterval.cents' to
'PitchInterval.interval', we should rather change the global naming
convention and use the unit instead of the property as the name:

Pitch.frequency                 =>      Pitch.hertz
Volume.amplitude                =>      Volume.decibel [1]
Duration.duration               =>      Duration.beat_count
TempoPoint.tempo                =>      TempoPoint.bpm
PitchInterval.interval          =>      PitchInterval.cents
Lyric.phonetic_representation   =>      Lyric.xampa

This rename has multiple advantages:

- We no longer have stupid repetitions ('duration.duration' or
  'interval.interval').

- It's more explicit now, as the unit of 'interval' could also be
  a multiplier or the unit of 'duration' could also be seconds.
  Now the names are less ambiguous.

- Most names are shorter.

This patch applies these changes in 'mutwo.core', e.g. it changes
'Duration' and 'TempoPoint'. When migrating to 'mutwo.core 2.0.0',
'mutwo.music' needs to apply the same change of naming convention
to its own parameters.

[1] This wouldn't only change the name, but also what this pivotal
    property is.
  • Loading branch information
levinericzimmermann committed Feb 26, 2024
1 parent c37ed72 commit 9f08dca
Show file tree
Hide file tree
Showing 11 changed files with 104 additions and 103 deletions.
6 changes: 3 additions & 3 deletions mutwo/core_converters/tempos.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def convert(
0.5
"""
t = core_parameters.abc.TempoPoint.from_any(tempo_point_to_convert)
return float(60 / t.tempo)
return float(60 / t.bpm)


class TempoConverter(core_converters.abc.EventConverter):
Expand Down Expand Up @@ -153,7 +153,7 @@ def _tempo_envelope_to_beat_length_in_seconds_envelope(
# ###################################################################### #

def _start_and_end_to_tempo_converter(self, start, end):
key = (start.duration, end.duration)
key = (start.beat_count, end.beat_count)
try:
t = self._start_and_end_to_tempo_converter_dict[key]
except KeyError:
Expand All @@ -169,7 +169,7 @@ def _start_and_end_to_tempo_converter(self, start, end):
def _integrate(
self, start: core_parameters.abc.Duration, end: core_parameters.abc.Duration
):
key = (start.duration, end.duration)
key = (start.beat_count, end.beat_count)
try:
i = self._start_and_end_to_integration[key]
except KeyError:
Expand Down
6 changes: 3 additions & 3 deletions mutwo/core_events/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,12 +715,12 @@ def duration(self, duration: core_parameters.abc.Duration.Type):
if not self: # If empty and duration == 0, we'd run into ZeroDivision
raise core_utilities.CannotSetDurationOfEmptyComplexEvent()

durf = core_parameters.abc.Duration.from_any(duration).duration
if (old_durf := self.duration.duration) != 0:
durf = core_parameters.abc.Duration.from_any(duration).beat_count
if (old_durf := self.duration.beat_count) != 0:

def f(event_duration: core_parameters.abc.Duration):
return core_utilities.scale(
event_duration.duration, 0, old_durf, 0, durf
event_duration.beat_count, 0, old_durf, 0, durf
)

else:
Expand Down
14 changes: 7 additions & 7 deletions mutwo/core_events/basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -363,7 +363,7 @@ def _split_child_at(
) -> int:
absolute_time = core_parameters.abc.Duration.from_any(absolute_time)
self._assert_valid_absolute_time(absolute_time)
abstf = absolute_time.duration
abstf = absolute_time.beat_count

event_index = Consecution._get_index_at_from_absolute_time_tuple(
abstf, abstf_tuple, durf
Expand Down Expand Up @@ -424,7 +424,7 @@ def _abstf_tuple_and_dur(
This property helps to improve performance of various functions
which uses duration and absolute_time_tuple attribute.
"""
d_iter = (e.duration.duration for e in self)
d_iter = (e.duration.beat_count for e in self)
abstf_tuple = tuple(
# We need to round each duration again after accumulation,
# because floats were summed which could lead to
Expand Down Expand Up @@ -503,7 +503,7 @@ def get_event_index_at(
This method ignores events with duration == 0.
"""
abstf = core_parameters.abc.Duration.from_any(absolute_time).duration
abstf = core_parameters.abc.Duration.from_any(absolute_time).beat_count
abstf_tuple, durf = self._abstf_tuple_and_dur
return Consecution._get_index_at_from_absolute_time_tuple(
abstf, abstf_tuple, durf
Expand Down Expand Up @@ -594,7 +594,7 @@ def squash_in( # type: ignore
) -> Consecution[T]:
start = core_parameters.abc.Duration.from_any(start)
self._assert_valid_absolute_time(start)
start_in_floats = start.duration
start_in_floats = start.beat_count
self._assert_start_in_range(start_in_floats)

# Only run cut_off if necessary -> Improve performance
Expand Down Expand Up @@ -646,7 +646,7 @@ def slide_in(
) -> Consecution[T]:
start = core_parameters.abc.Duration.from_any(start)
self._assert_valid_absolute_time(start)
start_in_floats = start.duration
start_in_floats = start.beat_count
if start_in_floats == 0:
self.insert(0, event_to_slide_in)
return self
Expand Down Expand Up @@ -675,7 +675,7 @@ def split_at(
raise core_utilities.NoSplitTimeError()

split_abstf_list = [
core_parameters.abc.Duration.from_any(t).duration for t in absolute_time
core_parameters.abc.Duration.from_any(t).beat_count for t in absolute_time
]

abstf_tuple, durf = self._abstf_tuple_and_dur
Expand Down Expand Up @@ -1084,7 +1084,7 @@ def sequentialize(
try: # Consecution
abst_tuple, dur = e._abstf_tuple_and_dur
except AttributeError: # Chronon or Concurrence
abst_tuple, dur = (0,), e.duration.duration
abst_tuple, dur = (0,), e.duration.beat_count
for t in abst_tuple + (dur,):
abst_set.add(t)

Expand Down
8 changes: 4 additions & 4 deletions mutwo/core_events/envelopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ def _curve_shape_at(
if e_idx is not None:
e = self[e_idx]
cs = self.event_to_curve_shape(e)
csx = ((abst - abst_tuple[e_idx]) / e.duration).duration * cs
csx = ((abst - abst_tuple[e_idx]) / e.duration).beat_count * cs
cs_at_abst = cs - csx
self.apply_curve_shape_on_event(e, csx)
else:
Expand All @@ -232,7 +232,7 @@ def _value_at(
abst_tuple: tuple["core_parameters.abc.Duration", ...],
dur: "core_parameters.abc.Duration",
):
abstf = abst.duration
abstf = abst.beat_count
abstf_tuple = tuple(map(float, abst_tuple))
durf = float(dur)

Expand Down Expand Up @@ -601,7 +601,7 @@ def get_average_value(
if duration == 0:
self._logger.warning(core_utilities.InvalidAverageValueStartAndEndWarning())
return self.value_at(start)
return self.integrate_interval(start, end) / duration.duration
return self.integrate_interval(start, end) / duration.beat_count

def get_average_parameter(
self,
Expand Down Expand Up @@ -871,7 +871,7 @@ def parameter_to_value(
# successful, if not it will return 'parameter', because it
# will assume that we have a number based tempo point.
return float(
getattr(parameter, "tempo", parameter)
getattr(parameter, "bpm", parameter)
)

def apply_parameter_on_event(
Expand Down
27 changes: 13 additions & 14 deletions mutwo/core_parameters/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,13 @@ def __lt__(self, other: typing.Any) -> bool:
return self._compare(other, lambda value0, value1: value0 < value1, True)


class Duration(SingleNumberParameter, value_name="duration", value_return_type="float"):
class Duration(SingleNumberParameter, value_name="beat_count", value_return_type="float"):
"""Abstract base class for any duration.
If the user wants to define a Duration class, the abstract
property :attr:`duration` has to be overridden.
property :attr:`beat_count` has to be overridden.
The attribute :attr:`duration` is stored in unit `beats`.
The ``duration`` of :mod:`mutwo` events are therefore not
The ``duration`` of :mod:`mutwo` events are not
related to a clear physical unit as for instance seconds.
The reason for this decision is to simplify musical usage.
"""
Expand All @@ -314,8 +312,8 @@ def _math_operation(
other: Duration | core_constants.Real,
operation: typing.Callable[[float, float], float],
) -> Duration:
self.duration = float(
operation(self.duration, getattr(other, "duration", other))
self.beat_count = float(
operation(self.beat_count, getattr(other, "beat_count", other))
)
return self

Expand Down Expand Up @@ -344,16 +342,16 @@ def __truediv__(self, other: Duration | core_constants.Real) -> Duration:
return self.copy().divide(other)

def __float__(self) -> float:
return self.duration
return self.beat_count

@property
@abc.abstractmethod
def duration(self) -> float:
def beat_count(self) -> float:
...

@duration.setter
@beat_count.setter
@abc.abstractmethod
def duration(self, duration: core_constants.Real):
def beat_count(self, beat_count: core_constants.Real):
...

@classmethod
Expand Down Expand Up @@ -385,12 +383,13 @@ def from_any(cls: typing.Type[T], object: Duration.Type) -> T:
raise core_utilities.CannotParseError(object, cls)


class TempoPoint(SingleNumberParameter, value_name="tempo", value_return_type="float"):
class TempoPoint(SingleNumberParameter, value_name="bpm", value_return_type="float"):
"""Represent the active tempo at a specific moment in time.
If the user wants to define a `TempoPoint` class, the abstract
property :attr:`tempo` needs to be overridden. Tempo should be
in the unit `beats per minute <https://en.wikipedia.org/wiki/Tempo#Measurement>`_.
property :attr:`bpm` needs to be overridden. ``BPM`` is an abbreviation
for 'beats per minute' and the unit of the parameter tempo, see more
information at this `wikipedia article <https://en.wikipedia.org/wiki/Tempo#Measurement>`_.
"""

Type: typing.TypeAlias = typing.Union["TempoPoint", core_constants.Real]
Expand Down
36 changes: 18 additions & 18 deletions mutwo/core_parameters/durations.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,23 +35,23 @@ class DirectDuration(core_parameters.abc.Duration):
**Example:**
>>> from mutwo import core_parameters
>>> # create duration with duration = 10 beats
>>> # create duration with beat_count = 10
>>> my_duration = core_parameters.DirectDuration(10)
>>> my_duration.duration
>>> my_duration.beat_count
10.0
"""

def __init__(self, duration: core_constants.Real):
self.duration = duration
def __init__(self, beat_count: core_constants.Real):
self.beat_count = beat_count

@property
def duration(self) -> float:
return self._duration
def beat_count(self) -> float:
return self._beat_count

@duration.setter
def duration(self, duration: core_constants.Real):
self._duration = core_utilities.round_floats(
float(duration),
@beat_count.setter
def beat_count(self, beat_count: core_constants.Real):
self._beat_count = core_utilities.round_floats(
float(beat_count),
core_parameters.configurations.ROUND_DURATION_TO_N_DIGITS,
)

Expand All @@ -70,7 +70,7 @@ class RatioDuration(core_parameters.abc.Duration):
R(2/3)
>>> d.ratio
Fraction(2, 3)
>>> d.duration
>>> d.beat_count
0.6666666667
"""

Expand All @@ -88,20 +88,20 @@ def ratio(self) -> fractions.Fraction:
def ratio(self, ratio: core_constants.Real | str):
self._ratio = fractions.Fraction(ratio)
try:
del self._duration
del self._beat_count
except AttributeError:
pass

@property
def duration(self) -> float:
return self._duration
def beat_count(self) -> float:
return self._beat_count

@duration.setter
def duration(self, duration: core_constants.Real | str):
self.ratio = duration
@beat_count.setter
def beat_count(self, beat_count: core_constants.Real | str):
self.ratio = beat_count

@functools.cached_property
def _duration(self) -> float:
def _beat_count(self) -> float:
return core_utilities.round_floats(
float(self.ratio),
core_parameters.configurations.ROUND_DURATION_TO_N_DIGITS,
Expand Down
40 changes: 20 additions & 20 deletions mutwo/core_parameters/tempos.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,22 @@ class DirectTempoPoint(core_parameters.abc.TempoPoint):
... ])
"""

def __init__(self, tempo: core_constants.Real | str):
self.tempo = tempo
def __init__(self, bpm: core_constants.Real | str):
self.bpm = bpm

@property
def tempo(self) -> float:
return self._tempo
def bpm(self) -> float:
return self._bpm

@tempo.setter
def tempo(self, tempo: core_constants.Real | str):
self._tempo = float(tempo)
@bpm.setter
def bpm(self, bpm: core_constants.Real | str):
self._bpm = float(bpm)


class WesternTempoPoint(core_parameters.abc.TempoPoint):
"""A tempo point useful for western notation.
:param tempo_range: Specify a tempo range in
:param bpm_range: Specify a tempo range in
`beats per minute <https://en.wikipedia.org/wiki/Tempo#Measurement>`_.
In western notation tempo is often indicated as a range from the
minimal accepted tempo to the fastest accepted tempo. Therefore
Expand Down Expand Up @@ -90,37 +90,37 @@ class WesternTempoPoint(core_parameters.abc.TempoPoint):

def __init__(
self,
tempo_range: ranges.Range | core_constants.Real | str,
bpm_range: ranges.Range | core_constants.Real | str,
reference: typing.Optional[core_constants.Real | str] = None,
textual_indication: typing.Optional[str] = None,
):
self.tempo_range = tempo_range
self.bpm_range = bpm_range
self.reference = reference or core_parameters.configurations.DEFAULT_REFERENCE
self.textual_indication = textual_indication

@property
def tempo(self) -> float:
return self._tempo_range.start * self.reference
def bpm(self) -> float:
return self._bpm_range.start * self.reference

@property
def tempo_range(self) -> ranges.Range:
def bpm_range(self) -> ranges.Range:
"""A range from the slowest to the fastest accepted tempo.
In internal calculations the minimal (slowest) tempo is used.
The tempo in the tempo range is relative as the absolute tempo
depends on the ``reference``.
"""
return self._tempo_range
return self._bpm_range

@tempo_range.setter
def tempo_range(self, tempo_range: ranges.Range | core_constants.Real | str):
match tempo_range:
@bpm_range.setter
def bpm_range(self, bpm_range: ranges.Range | core_constants.Real | str):
match bpm_range:
case ranges.Range():
r = tempo_range
r = bpm_range
case _:
v = float(tempo_range)
v = float(bpm_range)
r = ranges.Range(v, v)
self._tempo_range = r
self._bpm_range = r

@property
def reference(self) -> fractions.Fraction:
Expand Down
4 changes: 2 additions & 2 deletions performance_tests/performance_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ def setUp(self):
@t(0.06, 200)
def test_Consecution_split_at(self):
e = cons([ch(random.uniform(1, 3)) for _ in range(100)])
duration = e.duration.duration
duration = e.duration.beat_count
split_time_list = sorted([random.uniform(0, duration) for _ in range(100)])
e.split_at(*split_time_list)

@t(0.15, 100)
def test_Concurrence_split_at(self):
e = conc([ch(random.uniform(1, 3)) for _ in range(30)])
duration = e.duration.duration
duration = e.duration.beat_count
split_time_list = sorted([random.uniform(0, duration) for _ in range(25)])
e.split_at(*split_time_list)

Expand Down
4 changes: 2 additions & 2 deletions tests/events/basic_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ def test_copy(self):
chronon1 = chronon0.copy()
chronon1.duration = 300

self.assertEqual(chronon0.duration.duration, 20)
self.assertEqual(chronon1.duration.duration, 300)
self.assertEqual(chronon0.duration.beat_count, 20)
self.assertEqual(chronon1.duration.beat_count, 300)

def test_set(self):
chronon = core_events.Chronon(1)
Expand Down
Loading

0 comments on commit 9f08dca

Please sign in to comment.