Skip to content

Commit

Permalink
Merge pull request #1681 from cuthbertLab/volume_simplify
Browse files Browse the repository at this point in the history
Volume constructor to keyword only
  • Loading branch information
mscuthbert authored Jan 3, 2024
2 parents f1dab2f + 58d2927 commit 3941a7f
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 52 deletions.
34 changes: 15 additions & 19 deletions music21/chord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ def tie(self, value: tie.Tie|None):
# d['tie'] = value

@property
def volume(self) -> 'music21.volume.Volume': # do NOT change to volume.Volume
def volume(self) -> 'music21.volume.Volume': # do NOT change to volume.Volume, see setter...
'''
Get or set the :class:`~music21.volume.Volume` object for this
Chord.
Expand Down Expand Up @@ -457,28 +457,28 @@ def volume(self) -> 'music21.volume.Volume': # do NOT change to volume.Volume

if not self.hasComponentVolumes():
# create a single new Volume object for the chord
self._volume = note.NotRest._getVolume(self, forceClient=self)
self._volume = volume.Volume(client=self)
return self._volume

# if we have components and _volume is None, create a volume from
# components
velocities = []
for d in self._notes:
velocities.append(d.volume.velocity)
for inner_n in self._notes:
if inner_n.volume.velocity is not None:
velocities.append(inner_n.volume.velocity)
# create new local object
self._volume = volume.Volume(client=self)
out_volume = volume.Volume(client=self)
if velocities: # avoid division by zero error
self._volume.velocity = int(round(sum(velocities) / len(velocities)))
out_volume.velocity = int(round(sum(velocities) / len(velocities)))

if t.TYPE_CHECKING:
assert self._volume is not None
return self._volume
self._volume = out_volume
return out_volume


@volume.setter
def volume(self, expr: None|'music21.volume.Volume'|int|float):
# Do NOT change typing to volume.Volume because it will take the property as
# its name
# Do NOT change typing to volume.Volume w/o quotes because it will take the property as
# its name and be really confused.
if isinstance(expr, volume.Volume):
expr.client = self
# remove any component volumes
Expand All @@ -487,13 +487,10 @@ def volume(self, expr: None|'music21.volume.Volume'|int|float):
note.NotRest._setVolume(self, expr, setClient=False)
elif common.isNum(expr):
vol = self._getVolume()
if t.TYPE_CHECKING:
assert isinstance(expr, (int, float))

if expr < 1: # assume a scalar
vol.velocityScalar = expr
vol.velocityScalar = float(expr)
else: # assume velocity
vol.velocity = expr
vol.velocity = int(expr)
else:
raise ChordException(f'unhandled setting expr: {expr}')

Expand Down Expand Up @@ -528,7 +525,6 @@ def hasComponentVolumes(self) -> bool:
>>> c4.hasComponentVolumes()
False
'''
count = 0
for c in self._notes:
Expand Down Expand Up @@ -585,9 +581,9 @@ def setVolumes(self, volumes: Sequence['music21.volume.Volume'|int|float]):
v = v_entry
else: # create a new Volume
if v_entry < 1: # assume a scalar
v = volume.Volume(velocityScalar=v_entry)
v = volume.Volume(velocityScalar=float(v_entry))
else: # assume velocity
v = volume.Volume(velocity=v_entry)
v = volume.Volume(velocity=int(v_entry))
v.client = self
c._setVolume(v, setClient=False)

Expand Down
4 changes: 2 additions & 2 deletions music21/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -1279,9 +1279,9 @@ def _setVolume(self, value: None|volume.Volume|int|float, setClient=True):
# call local getVolume will set client appropriately
vol = self._getVolume()
if value < 1: # assume a scalar
vol.velocityScalar = value
vol.velocityScalar = float(value)
else: # assume velocity
vol.velocity = value
vol.velocity = int(value)

else:
raise TypeError(f'this must be a Volume object, not {value}')
Expand Down
84 changes: 53 additions & 31 deletions music21/volume.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
# Authors: Christopher Ariza
# Michael Scott Asato Cuthbert
#
# Copyright: Copyright © 2011-2012, 2015, 2017
# Copyright: Copyright © 2011-2012, 2015, 2017, 2024
# Michael Scott Asato Cuthbert
# License: BSD, see license.txt
# ------------------------------------------------------------------------------
Expand All @@ -34,31 +34,36 @@


# ------------------------------------------------------------------------------


class VolumeException(exceptions21.Music21Exception):
pass


# ------------------------------------------------------------------------------


class Volume(prebase.ProtoM21Object, SlottedObjectMixin):
'''
The Volume object lives on NotRest objects and subclasses. It is not a
Music21Object subclass.
>>> v = volume.Volume(velocity=90)
>>> v
<music21.volume.Volume realized=0.71>
>>> v.velocity
90
Generally, just assume that a Note has a volume object and don't worry
about creating this class directly:
>>> n = note.Note('C5')
>>> v = n.volume
>>> v.velocity = 20
>>> v.client is n
True
But if you want to create it yourself, you can specify the client, velocity,
velocityScalar, and
>>> v = volume.Volume(velocity=90)
>>> v
<music21.volume.Volume realized=0.71>
>>> v.velocity
90
* Changed in v9: all constructor attributes are keyword only.
(client as first attribute was confusing)
'''
# CLASS VARIABLES #
__slots__ = (
Expand All @@ -70,19 +75,20 @@ class Volume(prebase.ProtoM21Object, SlottedObjectMixin):

def __init__(
self,
*,
client: note.NotRest|None = None,
velocity=None,
velocityScalar=None,
velocityIsRelative=True,
velocity: int|None = None,
velocityScalar: float|None = None,
velocityIsRelative: bool = True,
):
# store a reference to the client, as we use this to do context
# will use property; if None will leave as None
self.client = client
self._velocityScalar = None
self._velocityScalar: float|None = None
if velocity is not None:
self.velocity = velocity
self.velocity = int(velocity)
elif velocityScalar is not None:
self.velocityScalar = velocityScalar
self.velocityScalar = float(velocityScalar)
self._cachedRealized = None
self.velocityIsRelative = velocityIsRelative

Expand All @@ -103,8 +109,14 @@ def getDynamicContext(self):
'''
Return the dynamic context of this Volume, based on the position of the
client of this object.
>>> n = note.Note()
>>> n.volume.velocityScalar = 0.9
>>> s = stream.Measure([dynamics.Dynamic('ff'), n])
>>> n.volume.getDynamicContext()
<music21.dynamics.Dynamic ff>
'''
# TODO: find wedges and crescendi too and demo/test.
# TODO: find wedges and crescendi too and demo/test.
return self.client.getContextByClass('Dynamic')

def mergeAttributes(self, other):
Expand All @@ -113,9 +125,8 @@ def mergeAttributes(self, other):
Values are always copied, not passed by reference.
>>> n1 = note.Note()
>>> v1 = volume.Volume()
>>> v1 = n1.volume
>>> v1.velocity = 111
>>> v1.client = n1
>>> v2 = volume.Volume()
>>> v2.mergeAttributes(v1)
Expand Down Expand Up @@ -249,8 +260,10 @@ def getRealized(
elif self.client is not None:
dm = self.getDynamicContext() # dm may be None
else:
environLocal.printDebug(['getRealized():',
'useDynamicContext is True but no dynamic supplied or found in context'])
environLocal.printDebug([
'getRealized():',
'useDynamicContext is True but no dynamic supplied or found in context',
])
if dm is not None:
# double scalar (so range is between 0 and 1) and scale
# the current val (around the base)
Expand Down Expand Up @@ -314,7 +327,7 @@ def realized(self):
return self.getRealized()

@property
def velocity(self):
def velocity(self) -> int|None:
'''
Get or set the velocity value, a numerical value between 0 and 127 and
available setting amplitude on each Note or Pitch in chord.
Expand All @@ -338,18 +351,20 @@ def velocity(self):
return round(v)

@velocity.setter
def velocity(self, value):
if not common.isNum(value):
def velocity(self, value: int|float|None):
if value is None:
self._velocityScalar = None
elif not common.isNum(value):
raise VolumeException(f'value provided for velocity must be a number, not {value}')
if value < 0:
elif value <= 0:
self._velocityScalar = 0.0
elif value > 127:
elif value >= 127:
self._velocityScalar = 1.0
else:
self._velocityScalar = value / 127.0

@property
def velocityScalar(self):
def velocityScalar(self) -> float|None:
'''
Get or set the velocityScalar value, a numerical value between 0
and 1 and available setting amplitude on each Note or Pitch in
Expand Down Expand Up @@ -384,16 +399,23 @@ def velocityScalar(self):
return v

@velocityScalar.setter
def velocityScalar(self, value):
def velocityScalar(self, value: int|float|None):
if value is None:
self._velocityScalar = None

if not common.isNum(value):
raise VolumeException('value provided for velocityScalar must be a number, '
+ f'not {value}')

scalar: float
if value < 0:
scalar = 0
scalar = 0.0
elif value > 1:
scalar = 1
scalar = 1.0
else:
scalar = value
if t.TYPE_CHECKING:
assert value is not None
scalar = float(value)
self._velocityScalar = scalar


Expand Down

0 comments on commit 3941a7f

Please sign in to comment.