Skip to content
Open
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
10 changes: 6 additions & 4 deletions music21/common/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,13 @@ def _check_init(self, key: str|None = None) -> bool:
if not getattr(self, 'frozen', True):
return True

for st in inspect.stack():
if (st.frame.f_code.co_name in ('__init__', '__new__', '__setstate__')
and 'self' in st.frame.f_locals
and st.frame.f_locals['self'].__class__ == self.__class__):
frame = inspect.currentframe()
while frame is not None:
if (frame.f_code.co_name in ('__init__', '__new__', '__setstate__')
and 'self' in frame.f_locals
and frame.f_locals['self'].__class__ == self.__class__):
return True
frame = frame.f_back
raise TypeError(f'This {self.__class__.__name__} instance is immutable.')

def __setattr__(self, key: str, value: t.Any) -> None:
Expand Down
23 changes: 9 additions & 14 deletions music21/meter/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@

from music21.meter.tools import slashToTuple, proportionToFraction
from music21.meter.core import (
MeterSequence, getMeterSequence, getMeterTerminal, makeSequence,
MeterSequence, getMeterSequence, getMeterTerminal,
)

environLocal = environment.Environment('meter')
Expand Down Expand Up @@ -1176,11 +1176,7 @@ def summedNumerator(self) -> bool:
@summedNumerator.setter
def summedNumerator(self, value: bool):
if self.displaySequence is not None:
# displaySequence is immutable; swap in a copy with the new flag
ds = self.displaySequence
self.displaySequence = makeSequence(
ds.numerator, ds.denominator, ds._partition,
parenthesis=ds.parenthesis, summedNumerator=value)
self.displaySequence = self.displaySequence.modify(summedNumerator=value)

# --------------------------------------------------------------------------
# private methods -- most to be put into the various sequences.
Expand Down Expand Up @@ -1341,14 +1337,13 @@ def _setDefaultAccentWeights(self, depth: int = 3) -> None:
weightValues[x + 1] = weightValueMin * pow(2, x)

# set weights on accent partitions
self.accentSequence = self.accentSequence.partition([1] * accentCount)
for i in range(accentCount):
# get values from weightValues dictionary; terminals are
# immutable, so replace each with a re-weighted cached one
mt = self.accentSequence[i]
self.accentSequence = self.accentSequence.setIndex(
i, getMeterTerminal(
mt.numerator, mt.denominator, weight=weightValues[weightInts[i]]))
accentSequence = self.accentSequence.partition([1] * accentCount)
weighted = []
for i, mt in enumerate(accentSequence):
# get values from weightValues dictionary
weighted.append(getMeterTerminal(
mt.numerator, mt.denominator, weight=weightValues[weightInts[i]]))
self.accentSequence = accentSequence.modify(partition=weighted)

# --------------------------------------------------------------------------
# access data for other processing
Expand Down
40 changes: 23 additions & 17 deletions music21/meter/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,6 @@ def duration(self) -> FrozenDuration:
'''
return self._durationFromNumeratorDenominator(self.numerator, self.denominator)

def _reprInternal(self):
return str(self)

def ratioEqual(self, other) -> bool:
'''
Compare the numerator and denominator of another object.
Expand Down Expand Up @@ -2253,19 +2250,26 @@ def _getAtPath(root: MeterSequence, path: tuple[int, ...]) -> MeterNode:
return node


def _replaceAtPath(root: MeterSequence, path: tuple[int, ...], newNode) -> MeterSequence:
def _replaceAtPaths(
node: MeterNode,
replacements: dict[tuple[int, ...], MeterNode],
ancestors: set[tuple[int, ...]],
path: tuple[int, ...] = (),
) -> MeterNode:
'''
Return a new root MeterSequence with the node at `path` replaced by
`newNode`, rebuilding each ancestor functionally.
Return `node` with each node at a path in `replacements` replaced, rebuilding
only the sequences in `ancestors`, the paths above a replacement.

AI-assisted (Claude).
'''
idx = path[0]
if len(path) == 1:
return root.setIndex(idx, newNode)
child = t.cast('MeterSequence', root[idx])
newChild = _replaceAtPath(child, path[1:], newNode)
return root.setIndex(idx, newChild)
if path in replacements:
return replacements[path]
if path not in ancestors:
return node
sequence = t.cast('MeterSequence', node)
newPartition = [_replaceAtPaths(child, replacements, ancestors, path + (i,))
for i, child in enumerate(sequence)]
return sequence._rebuild(newPartition)


def _subdivideNestedByPaths(
Expand All @@ -2274,20 +2278,22 @@ def _subdivideNestedByPaths(
divisions,
) -> tuple[MeterSequence, list[tuple[int, ...]]]:
'''
Subdivide (equally) each node reached by a path in `paths`, rebuilding the
root functionally, and return the new root plus the list of paths to the
newly created child nodes.
Subdivide each node at `paths` equally. No path may lie inside another.
Return the new root and the paths to the new child nodes.

AI-assisted (Claude).
'''
replacements: dict[tuple[int, ...], MeterNode] = {}
newPaths: list[tuple[int, ...]] = []
for p in paths:
node = t.cast('MeterSequence', _getAtPath(root, p))
newNode = node.subdividePartitionsEqual(divisions)
root = _replaceAtPath(root, p, newNode)
replacements[p] = newNode
for j in range(len(newNode)):
newPaths.append(p + (j,))
return root, newPaths
ancestors = {p[:k] for p in paths for k in range(len(p))}
newRoot = t.cast('MeterSequence', _replaceAtPaths(root, replacements, ancestors))
return newRoot, newPaths


if __name__ == '__main__':
Expand Down
Loading