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
62 changes: 29 additions & 33 deletions documentation/source/usersGuide/usersGuide_55_advancedMeter.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -181,17 +181,17 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"A MeterSequence object is a sub-class of a MeterTerminal. Like a MeterTerminal, a MeterSequence has a :attr:`numerator <music21.meter.MeterSequence.numerator>`, a :attr:`denominator <music21.meter.MeterSequence.denominator>`, and a :attr:`duration <music21.meter.MeterTerminal.duration>` attribute. A MeterSequence, however, can be a hierarchical tree or sub-tree, containing an ordered sequence of MeterTerminal and/or MeterSequence objects.\n",
"Like a MeterTerminal, a MeterSequence has a :attr:`numerator <music21.meter.MeterSequence.numerator>`, a :attr:`denominator <music21.meter.MeterSequence.denominator>`, and a :attr:`duration <music21.meter.MeterTerminal.duration>` attribute. A MeterSequence, however, can be a hierarchical tree or sub-tree, containing an ordered sequence of MeterTerminal and/or MeterSequence objects.\n",
"\n",
"The ordered collection of MeterTerminal and/or MeterSequence objects can be accessed like Python lists. MeterSequence objects, like MeterTerminal objects, store a weight that by default is the sum of constituent weights.\n",
"\n",
"The :meth:`~music21.meter.MeterSequence.partition` and :meth:`~music21.meter.MeterTerminal.subdivide` methods can be used to configure the nested hierarchical structure.\n",
"MeterTerminal and MeterSequence objects cannot be changed. The :meth:`~music21.meter.MeterSequence.partition`, :meth:`~music21.meter.MeterTerminal.subdivide`, and :meth:`~music21.meter.MeterSequence.setIndex` methods each return a new object, which can be assigned to a variable or to a TimeSignature attribute, to configure the nested hierarchical structure.\n",
"\n",
"The :meth:`~music21.meter.MeterSequence.partition` method replaces existing MeterTerminal or MeterSequence objects in place with a new arrangement, specified as a desired number of equal-valued components, a list of numerators assuming equal-denominators, or a list of string fraction representations.\n",
"The :meth:`~music21.meter.MeterSequence.partition` method returns a new MeterSequence with a new arrangement, specified as a desired number of equal-valued components, a list of numerators assuming equal-denominators, or a list of string fraction representations.\n",
"\n",
"The :meth:`~music21.meter.MeterTerminal.subdivide` method returns a new MeterSequence (leaving the source MeterSequence unchanged) with an arrangement of MeterTerminals as specified by an argument in the same form as for the :meth:`~music21.meter.MeterSequence.partition` method.\n",
"\n",
"Note that MeterTerminal objects cannot be partitioned in place. A common way to convert a MeterTerminal into a MeterSequence is to reassign the returned MeterSequence from the :meth:`~music21.meter.MeterTerminal.subdivide` method to the position occupied by the MeterTerminal.\n",
"The :meth:`~music21.meter.MeterSequence.setIndex` method returns a new MeterSequence with one position replaced by an object of the same length. A common way to convert a MeterTerminal into a MeterSequence is to pass the MeterSequence returned from the :meth:`~music21.meter.MeterTerminal.subdivide` method to :meth:`~music21.meter.MeterSequence.setIndex` for the position occupied by the MeterTerminal.\n",
"\n",
"The following example creates and partitions a MeterSequence by re-assigning subdivisions to MeterTerminal objects. The use of Python list-like index access is also demonstrated.\n"
]
Expand Down Expand Up @@ -234,7 +234,7 @@
}
],
"source": [
"ms.partition([3,3])\n",
"ms = ms.partition([3, 3])\n",
"ms"
]
},
Expand Down Expand Up @@ -275,7 +275,7 @@
}
],
"source": [
"ms[0] = ms[0].subdivide([3,3])\n",
"ms = ms.setIndex(0, ms[0].subdivide([3, 3]))\n",
"ms[0]"
]
},
Expand Down Expand Up @@ -316,7 +316,7 @@
}
],
"source": [
"ms[1] = ms[1].subdivide([1,1,1])\n",
"ms = ms.setIndex(1, ms[1].subdivide([1, 1, 1]))\n",
"ms[1][0]"
]
},
Expand Down Expand Up @@ -748,9 +748,9 @@
"source": [
"from music21 import stream, note\n",
"ts1 = meter.TimeSignature('5/8') # assumes two partitions\n",
"ts1.displaySequence.partition(['3/16', '1/8', '5/16'])\n",
"ts1.displaySequence = ts1.displaySequence.partition(['3/16', '1/8', '5/16'])\n",
"ts2 = meter.TimeSignature('5/8') # assumes two partitions\n",
"ts2.displaySequence.partition(['2/8', '3/8'])\n",
"ts2.displaySequence = ts2.displaySequence.partition(['2/8', '3/8'])\n",
"ts2.summedNumerator = True\n",
"s = stream.Stream()\n",
"for ts in [ts1, ts2]:\n",
Expand Down Expand Up @@ -807,24 +807,26 @@
],
"source": [
"ts1 = meter.TimeSignature('3/4')\n",
"ts1.beamSequence.partition(1)\n",
"ts1.beamSequence[0] = ts1.beamSequence[0].subdivide(['3/8', '5/32', '4/32', '3/32'])\n",
"ts1.beamSequence = ts1.beamSequence.partition(1)\n",
"ts1.beamSequence = ts1.beamSequence.setIndex(\n",
" 0, ts1.beamSequence[0].subdivide(['3/8', '5/32', '4/32', '3/32']))\n",
"\n",
"ts2 = meter.TimeSignature('3/4')\n",
"ts2.beamSequence.partition(3)\n",
"ts2.beamSequence = ts2.beamSequence.partition(3)\n",
"\n",
"ts3 = meter.TimeSignature('3/4')\n",
"ts3.beamSequence.partition(3)\n",
"\n",
"ts3.beamSequence = ts3.beamSequence.partition(3)\n",
"for i in range(len(ts3.beamSequence)):\n",
" ts3.beamSequence[i] = ts3.beamSequence[i].subdivide(2)\n",
" ts3.beamSequence = ts3.beamSequence.setIndex(i, ts3.beamSequence[i].subdivide(2))\n",
"\n",
"ts4 = meter.TimeSignature('3/4')\n",
"ts4.beamSequence.partition(['3/8', '3/8'])\n",
"for i in range(len(ts4.beamSequence)):\n",
" ts4.beamSequence[i] = ts4.beamSequence[i].subdivide(['6/32', '6/32'])\n",
" for j in range(len(ts4.beamSequence[i])):\n",
" ts4.beamSequence[i][j] = ts4.beamSequence[i][j].subdivide(2)\n",
"beams = ts4.beamSequence.partition(['3/8', '3/8'])\n",
"for i in range(len(beams)):\n",
" group = beams[i].subdivide(['6/32', '6/32'])\n",
" for j in range(len(group)):\n",
" group = group.setIndex(j, group[j].subdivide(2))\n",
" beams = beams.setIndex(i, group)\n",
"ts4.beamSequence = beams\n",
"\n",
"s = stream.Stream()\n",
"for ts in [ts1, ts2, ts3, ts4]:\n",
Expand Down Expand Up @@ -932,7 +934,7 @@
{
"data": {
"text/plain": [
"<music21.duration.Duration 1.0>"
"<music21.duration.FrozenDuration 1.0>"
]
},
"execution_count": 35,
Expand Down Expand Up @@ -1093,7 +1095,7 @@
{
"data": {
"text/plain": [
"<music21.duration.Duration 0.75>"
"<music21.duration.FrozenDuration 0.75>"
]
},
"execution_count": 43,
Expand Down Expand Up @@ -1245,7 +1247,7 @@
"source": [
"score = corpus.parse('bach/bwv366.xml')\n",
"ts = score[meter.TimeSignature].first()\n",
"ts.beatSequence.partition(3)\n",
"ts.beatSequence = ts.beatSequence.partition(3)\n",
"\n",
"found = stream.Stream()\n",
"offsetQL = 0\n",
Expand Down Expand Up @@ -1278,7 +1280,7 @@
"\n",
"The :meth:`~music21.meter.TimeSignature.getBeatDepth` method, when set with the optional parameter `align` to \"quantize\", shows the number of hierarchical levels that start at or before that point. This value is described by Lerdahl and Jackendoff as metrical analysis.\n",
"\n",
"In the following example, :meth:`beatSequence <music21.meter.TimeSignature.beatSequence>` MeterSequence is partitioned first into one subdivision, and then each subsequent subdivision into two, down to four layers of partitioning.\n",
"In the following example, :meth:`beatSequence <music21.meter.TimeSignature.beatSequence>` MeterSequence is partitioned first into one subdivision, and then each subsequent subdivision into two, down to four layers of partitioning, with the :meth:`~music21.meter.MeterSequence.subdivideNestedHierarchy` method.\n",
"\n",
"The number of hierarchical levels, found with the :meth:`~music21.meter.TimeSignature.getBeatDepth` method, is appended to each note with the :meth:`~music21.note.GeneralNote.addLyric` method."
]
Expand Down Expand Up @@ -1308,13 +1310,7 @@
"score = corpus.parse('bach/bwv281.xml')\n",
"partBass = score.getElementById('Bass')\n",
"ts = partBass[meter.TimeSignature].first()\n",
"ts.beatSequence.partition(1)\n",
"for h in range(len(ts.beatSequence)):\n",
" ts.beatSequence[h] = ts.beatSequence[h].subdivide(2)\n",
" for i in range(len(ts.beatSequence[h])):\n",
" ts.beatSequence[h][i] = ts.beatSequence[h][i].subdivide(2)\n",
" for j in range(len(ts.beatSequence[h][i])):\n",
" ts.beatSequence[h][i][j] = ts.beatSequence[h][i][j].subdivide(2)\n",
"ts.beatSequence = ts.beatSequence.subdivideNestedHierarchy(3)\n",
"\n",
"for m in partBass.getElementsByClass(stream.Measure):\n",
" for n in m.notesAndRests:\n",
Expand Down Expand Up @@ -1388,8 +1384,8 @@
"score = corpus.parse('bach/bwv366.xml')\n",
"partBass = score.getElementById('Bass')\n",
"ts = partBass[meter.TimeSignature].first()\n",
"ts.beatSequence.partition(['3/8', '3/8'])\n",
"ts.accentSequence.partition(['3/8', '3/8'])\n",
"ts.beatSequence = ts.beatSequence.partition(['3/8', '3/8'])\n",
"ts.accentSequence = ts.accentSequence.partition(['3/8', '3/8'])\n",
"ts.setAccentWeight([1, .5])\n",
"for m in partBass[stream.Measure]:\n",
" lastBeat = None\n",
Expand Down
38 changes: 34 additions & 4 deletions music21/meter/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,10 +525,10 @@ class MeterSequence(prebase.ProtoM21Object, MeterCore, FrozenObject):

* Changed in v11: MeterSequence is immutable, hashable, deep-copies to
itself, and its formerly in-place operations return a new sequence.
Removed `load()` (set values when creating the sequence),
`subdivideByOther()` (it only wrapped its argument in a new sequence),
the `weight` setter (use `withWeight()`), and item assignment
`ms[i] = x` (use `setIndex()`).
`load()` raises a MeterException (set values when creating the
sequence) and item assignment `ms[i] = x` raises a TypeError (use
`setIndex()`). Removed `subdivideByOther()` (it only wrapped its
argument in a new sequence) and the `weight` setter (use `withWeight()`).

AI-assisted (Claude).
'''
Expand Down Expand Up @@ -691,6 +691,36 @@ def __len__(self):
'''
return len(self._partition)

def __setitem__(self, key: int, value: MeterNode) -> t.NoReturn:
'''
Raises a TypeError: a MeterSequence is immutable. Use :meth:`setIndex`.

>>> a = meter.MeterSequence('4/4', 4)
>>> a[0] = a[0].subdivide(2)
Traceback (most recent call last):
TypeError: MeterSequence is immutable: use ms = ms.setIndex(0, value)

* Changed in v11: raises a TypeError.
'''
raise TypeError(f'MeterSequence is immutable: use ms = ms.setIndex({key}, value)')

def load(self, *arguments, **keywords) -> t.NoReturn:
'''
Raises a MeterException: a MeterSequence is immutable. Pass the value
to the constructor or to :func:`getMeterSequence` instead.

>>> ms = meter.MeterSequence()
>>> ms.load('4/4')
Traceback (most recent call last):
music21.exceptions21.MeterException: MeterSequence is immutable, so load()
cannot change it: use ms = meter.MeterSequence('4/4')

* Changed in v11: no longer changes the sequence in place.
'''
value = repr(arguments[0]) if arguments else '...'
raise MeterException('MeterSequence is immutable, so load() cannot change it: '
f'use ms = meter.MeterSequence({value})')

def setIndex(self, index: int, value: MeterNode) -> MeterSequence:
'''
Return a new MeterSequence with the element at `index` replaced by
Expand Down
21 changes: 21 additions & 0 deletions music21/meter/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,27 @@ def testMeterSequenceModify(self):
with self.assertRaises(ValueError):
a.modify(**kw)

def testInPlaceFormsRaise(self):
from music21.exceptions21 import MeterException
ts = TimeSignature('6/8')
before = ts.beatSequence
with self.assertRaisesRegex(MeterException, r"MeterSequence\('4/4'\)"):
MeterSequence().load('4/4')
with self.assertRaisesRegex(TypeError, r'setIndex\(0, value\)'):
ts.beatSequence[0] = ts.beatSequence[0].subdivide(3)

mt = MeterTerminal('3/4')
for attribute, value in (('numerator', 2), ('denominator', 8), ('weight', 0.5),
('duration', duration.Duration(2.0))):
with self.assertRaises(TypeError, msg=attribute):
setattr(mt, attribute, value)
for attribute, value in (('weight', 0.5), ('parenthesis', True),
('summedNumerator', True)):
with self.assertRaises(TypeError, msg=attribute):
setattr(ts.beatSequence, attribute, value)
self.assertIs(ts.beatSequence, before)
self.assertEqual(str(mt), '3/4')

def testGetBeams(self):
ts = TimeSignature('6/8')
durList = [16, 16, 16, 16, 8, 16, 16, 16, 16, 8]
Expand Down
Loading