Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Volume constructor to keyword only #1681

Merged
merged 6 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading