"""
Tools for modeling musical meter.
"""
import bisect
import collections
import fractions
import uqbar.graphs
from . import _getlib, _iterlib
from . import duration as _duration
from . import indicators as _indicators
from . import lilypondfile as _lilypondfile
from . import math as _math
from . import mutate as _mutate
from . import parentage as _parentage
from . import rhythmtrees as _rhythmtrees
from . import score as _score
from . import select as _select
from . import sequence as _sequence
from . import timespan as _timespan
[docs]class Meter:
"""
Meter models a common practice understanding of beats and other levels of rhythmic
organization structured as a tree. Meter structure corresponds to the monotonically
increasing sequence of factors in the numerator of a given time signature.
Successively deeper levels of the tree divide time by successive factors.
.. container:: example
Duple meter:
>>> meter = abjad.Meter((2, 4))
>>> meter
Meter('(2/4 (1/4 1/4))')
>>> print(meter.pretty_rtm_format)
(2/4 (
1/4
1/4))
>>> abjad.graph(meter) # doctest: +SKIP
``2/4`` comprises two beats.
.. container:: example
Triple meter:
>>> meter = abjad.Meter((3, 4))
>>> print(meter.pretty_rtm_format)
(3/4 (
1/4
1/4
1/4))
>>> abjad.graph(meter) # doctest: +SKIP
``3/4`` comprises three beats.
.. container:: example
Quadruple meter:
>>> meter = abjad.Meter((4, 4))
>>> meter
Meter('(4/4 (1/4 1/4 1/4 1/4))')
>>> print(meter.pretty_rtm_format)
(4/4 (
1/4
1/4
1/4
1/4))
>>> abjad.graph(meter) # doctest: +SKIP
``4/4`` comprises four beats.
.. container:: example
Compound triple meter:
>>> meter = abjad.Meter((6, 8))
>>> print(meter.pretty_rtm_format)
(6/8 (
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))))
>>> abjad.graph(meter) # doctest: +SKIP
``6/8`` comprises two beats of three parts each.
.. container:: example
Another compound triple meter:
>>> meter = abjad.Meter((12, 8))
>>> print(meter.pretty_rtm_format)
(12/8 (
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))))
>>> abjad.graph(meter) # doctest: +SKIP
``12/8`` comprises four beats of three parts each.
.. container:: example
An asymmetric meter:
>>> meter = abjad.Meter((5, 4))
>>> print(meter.pretty_rtm_format)
(5/4 (
(3/4 (
1/4
1/4
1/4))
(2/4 (
1/4
1/4))))
>>> abjad.graph(meter) # doctest: +SKIP
``5/4`` comprises two unequal beats. By default unequal beats
are arranged from greatest to least.
.. container:: example
Another asymmetric meter:
>>> meter = abjad.Meter((7, 4))
>>> print(meter.pretty_rtm_format)
(7/4 (
(3/4 (
1/4
1/4
1/4))
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))))
>>> abjad.graph(meter) # doctest: +SKIP
``7/4`` comprises three unequal beats. Beats are arranged from
greatest to least by default.
.. container:: example
The same asymmetric meter structured differently:
>>> meter = abjad.Meter(
... (7, 4),
... increase_monotonic=True,
... )
>>> print(meter.pretty_rtm_format)
(7/4 (
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))
(3/4 (
1/4
1/4
1/4))))
>>> abjad.graph(meter) # doctest: +SKIP
``7/4`` with beats arragned from least to greatest.
.. container:: example
Meter interpreted by default as containing two compound beats:
>>> meter = abjad.Meter((6, 4))
>>> meter
Meter('(6/4 ((3/4 (1/4 1/4 1/4)) (3/4 (1/4 1/4 1/4))))')
>>> print(meter.pretty_rtm_format)
(6/4 (
(3/4 (
1/4
1/4
1/4))
(3/4 (
1/4
1/4
1/4))))
>>> abjad.graph(meter) # doctest: +SKIP
Same meter customized to contain four compound beats:
>>> parser = abjad.rhythmtrees.RhythmTreeParser()
>>> meter = abjad.Meter('(6/4 ((3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8))))')
>>> meter
Meter('(6/4 ((3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8)) (3/8 (1/8 1/8 1/8))))')
>>> print(meter.pretty_rtm_format)
(6/4 (
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))))
>>> abjad.graph(meter) # doctest: +SKIP
Prime divisions greater than ``3`` are converted to sequences of ``2`` and ``3``
summing to that prime. Summands are arranged from greatest to least by default. This
means that ``5`` becomes ``3+2`` and ``7`` becomes ``3+2+2`` in the examples above.
"""
### CLASS VARIABLES ###
__slots__ = (
"_increase_monotonic",
"_denominator",
"_numerator",
"_preferred_boundary_depth",
"_root_node",
)
### INITIALIZER ###
def __init__(
self,
argument=(4, 4),
increase_monotonic=None,
preferred_boundary_depth=None,
):
assert isinstance(preferred_boundary_depth, int | type(None))
self._preferred_boundary_depth = preferred_boundary_depth
def recurse(node, factors, denominator, increase_monotonic):
if factors:
factor, factors = factors[0], factors[1:]
assert isinstance(factor, int), repr(factor)
assert isinstance(node.preprolated_duration, tuple)
pair = _duration.divide_pair(node.preprolated_duration, factor)
if factor in (2, 3, 4):
if factors:
for _ in range(factor):
child = _rhythmtrees.RhythmTreeContainer(
preprolated_duration=pair
)
node.append(child)
recurse(child, factors, denominator, increase_monotonic)
else:
for _ in range(factor):
node.append(
_rhythmtrees.RhythmTreeLeaf(
preprolated_duration=(1, denominator)
)
)
else:
parts = [3]
total = 3
while total < factor:
if not increase_monotonic:
parts.append(2)
else:
parts.insert(0, 2)
total += 2
for part in parts:
assert isinstance(part, int)
grouping = _rhythmtrees.RhythmTreeContainer(
preprolated_duration=(part * pair[0], pair[1])
)
if factors:
for _ in range(part):
child = _rhythmtrees.RhythmTreeContainer(
preprolated_duration=pair
)
grouping.append(child)
recurse(
child,
factors,
denominator,
increase_monotonic,
)
else:
for _ in range(part):
grouping.append(
_rhythmtrees.RhythmTreeLeaf(
preprolated_duration=(1, denominator)
)
)
node.append(grouping)
else:
node.extend(
[
_rhythmtrees.RhythmTreeLeaf(
preprolated_duration=(1, denominator)
)
for _ in range(node.pair[0])
]
)
increase_monotonic = bool(increase_monotonic)
try:
numerator = argument.numerator
denominator = argument.denominator
is_fraction_like = True
except AttributeError:
is_fraction_like = False
if isinstance(argument, type(self)):
root = argument.root_node
numerator, denominator = argument.numerator, argument.denominator
increase_monotonic = argument.increase_monotonic
elif isinstance(argument, str | _rhythmtrees.RhythmTreeContainer):
if isinstance(argument, str):
parsed = _rhythmtrees.RhythmTreeParser()(argument)
assert len(parsed) == 1
root = parsed[0]
else:
root = argument
for node in [root] + list(root.depth_first()):
assert node.prolation == 1
numerator, denominator = root.pair
elif is_fraction_like or isinstance(argument, tuple):
if isinstance(argument, tuple):
numerator, denominator = argument
else:
numerator, denominator = argument.pair
pair = (numerator, denominator)
factors = _math.factors(numerator)
# group two nested levels of 2s into a 4
if 1 < len(factors) and factors[0] == factors[1] == 2:
factors[0:2] = [4]
root = _rhythmtrees.RhythmTreeContainer(preprolated_duration=pair)
recurse(root, factors, denominator, increase_monotonic)
else:
name = type(self).__name__
raise ValueError(f"can not initialize {name}: {argument!r}.")
self._root_node = root
self._numerator = numerator
self._denominator = denominator
self._increase_monotonic = increase_monotonic
### SPECIAL METHODS ###
[docs] def __eq__(self, argument):
"""
Compares ``numerator``, ``denominator``, ``increase_monotonic``,
``preferred_boundary_depth``.
"""
if isinstance(argument, type(self)):
return (
self.numerator == argument.numerator
and self.denominator == argument.denominator
and self.increase_monotonic == argument.increase_monotonic
and self.preferred_boundary_depth == argument.preferred_boundary_depth
)
return False
[docs] def __graph__(self, **keywords):
"""
Gets Graphviz format of meter.
.. container:: example
Graphs ``7/4``:
>>> meter = abjad.Meter((7, 4))
>>> meter_graph = meter.__graph__()
>>> abjad.graph(meter_graph) # doctest: +SKIP
.. docs::
>>> print(format(meter_graph, 'graphviz'))
digraph G {
graph [bgcolor=transparent,
fontname=Arial,
penwidth=2,
truecolor=true];
node [fontname=Arial,
fontsize=12,
penwidth=2];
edge [penwidth=2];
node_0 [label="7/4",
shape=triangle];
node_1 [label="3/4",
shape=triangle];
node_2 [label="1/4",
shape=box];
node_3 [label="1/4",
shape=box];
node_4 [label="1/4",
shape=box];
node_5 [label="2/4",
shape=triangle];
node_6 [label="1/4",
shape=box];
node_7 [label="1/4",
shape=box];
node_8 [label="2/4",
shape=triangle];
node_9 [label="1/4",
shape=box];
node_10 [label="1/4",
shape=box];
subgraph cluster_offsets {
graph [style=rounded];
node_11_0 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 0 | <f_0_1> +++ }",
shape=Mrecord,
style=filled];
node_11_1 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 1/4 | <f_0_1> + }",
shape=Mrecord,
style=filled];
node_11_2 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 1/2 | <f_0_1> + }",
shape=Mrecord,
style=filled];
node_11_3 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 3/4 | <f_0_1> ++ }",
shape=Mrecord,
style=filled];
node_11_4 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 1 | <f_0_1> + }",
shape=Mrecord,
style=filled];
node_11_5 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 5/4 | <f_0_1> ++ }",
shape=Mrecord,
style=filled];
node_11_6 [color=white,
fillcolor=black,
fontcolor=white,
fontname="Arial bold",
label="{ <f_0_0> 3/2 | <f_0_1> + }",
shape=Mrecord,
style=filled];
node_11_7 [label="{ <f_0_0> 7/4 | <f_0_1> +++ }",
shape=Mrecord];
}
node_0 -> node_1;
node_0 -> node_5;
node_0 -> node_8;
node_1 -> node_2;
node_1 -> node_3;
node_1 -> node_4;
node_2 -> node_11_0 [style=dotted];
node_2 -> node_11_1 [style=dotted];
node_3 -> node_11_1 [style=dotted];
node_3 -> node_11_2 [style=dotted];
node_4 -> node_11_2 [style=dotted];
node_4 -> node_11_3 [style=dotted];
node_5 -> node_6;
node_5 -> node_7;
node_6 -> node_11_3 [style=dotted];
node_6 -> node_11_4 [style=dotted];
node_7 -> node_11_4 [style=dotted];
node_7 -> node_11_5 [style=dotted];
node_8 -> node_9;
node_8 -> node_10;
node_9 -> node_11_5 [style=dotted];
node_9 -> node_11_6 [style=dotted];
node_10 -> node_11_6 [style=dotted];
node_10 -> node_11_7 [style=dotted];
}
Returns Graphviz graph.
"""
def make_offset_node(offset, leaf_one=None, leaf_two=None, is_last=False):
offset = _duration.Offset(offset)
if not is_last:
offset_node = uqbar.graphs.Node(
attributes={
"shape": "Mrecord",
"style": "filled",
"color": "white",
"fontname": "Arial bold",
"fontcolor": "white",
"fillcolor": "black",
}
)
else:
offset_node = uqbar.graphs.Node(attributes={"shape": "Mrecord"})
offset_field = uqbar.graphs.RecordField(label=str(offset))
weight_field = uqbar.graphs.RecordField(label="+" * offsets.items[offset])
group = uqbar.graphs.RecordGroup()
group.extend([offset_field, weight_field])
offset_node.append(group)
offset_subgraph.append(offset_node)
leaf_one_node = node_mapping[leaf_one]
edge = uqbar.graphs.Edge(attributes={"style": "dotted"})
edge.attach(leaf_one_node, offset_node)
if leaf_two:
leaf_two_node = node_mapping[leaf_two]
edge = uqbar.graphs.Edge(attributes={"style": "dotted"})
edge.attach(leaf_two_node, offset_node)
offsets = MetricAccentKernel.count_offsets(
_sequence.flatten(self.depthwise_offset_inventory, depth=-1)
)
graph = uqbar.graphs.Graph(
name="G",
attributes={
"bgcolor": "transparent",
"fontname": "Arial",
"penwidth": 2,
"truecolor": True,
},
edge_attributes={"penwidth": 2},
node_attributes={"fontname": "Arial", "fontsize": 12, "penwidth": 2},
)
node_mapping = {}
root = self._root_node
nodes = [root] + list(root.depth_first())
leaves = [_ for _ in nodes if not hasattr(_, "children")]
for node in nodes:
graphviz_node = uqbar.graphs.Node()
graphviz_node.attributes["label"] = node._get_fraction_string()
if isinstance(node, _rhythmtrees.RhythmTreeContainer):
graphviz_node.attributes["shape"] = "triangle"
else:
graphviz_node.attributes["shape"] = "box"
graph.append(graphviz_node)
node_mapping[node] = graphviz_node
if node.parent is not None:
uqbar.graphs.Edge().attach(
node_mapping[node.parent], node_mapping[node]
)
offset = leaves[0].start_offset
offset_subgraph = uqbar.graphs.Graph(
name="cluster_offsets", attributes={"style": "rounded"}
)
graph.append(offset_subgraph)
make_offset_node(offset, leaves[0])
for one, two in _sequence.nwise(leaves):
offset = one.stop_offset
make_offset_node(offset, one, two)
offset = leaves[-1].stop_offset
make_offset_node(offset, leaves[-1], is_last=True)
return graph
[docs] def __hash__(self):
"""
Hashes meter.
Returns integer.
"""
return super().__hash__()
[docs] def __iter__(self):
"""
Iterates meter.
.. container:: example
Iterates ``5/4``:
>>> meter = abjad.Meter((5, 4))
>>> for pair in meter:
... pair
...
((0, 4), (1, 4))
((1, 4), (2, 4))
((2, 4), (3, 4))
((0, 4), (3, 4))
((3, 4), (4, 4))
((4, 4), (5, 4))
((3, 4), (5, 4))
((0, 4), (5, 4))
Yields pairs.
"""
def recurse(node):
result = []
for child in node:
if isinstance(child, _rhythmtrees.RhythmTreeLeaf):
result.append(child)
else:
result.extend(recurse(child))
result.append(node)
return result
result = recurse(self.root_node)
for node in result:
pair = _duration.with_denominator(node.start_offset, self.denominator)
start_offset = pair
pair = _duration.with_denominator(node.stop_offset, self.denominator)
stop_offset = pair
yield start_offset, stop_offset
[docs] def __repr__(self) -> str:
"""
Gets repr.
"""
return f"{type(self).__name__}({self.rtm_format!r})"
### PUBLIC PROPERTIES ###
@property
def denominator(self):
"""
Gets denominator of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> meter.denominator
4
Returns positive integer.
"""
return self._denominator
@property
def depthwise_offset_inventory(self):
"""
Gets depthwise offset inventory of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> for depth, offsets in enumerate(
... meter.depthwise_offset_inventory):
... print(depth, offsets)
0 (Offset((0, 1)), Offset((7, 4)))
1 (Offset((0, 1)), Offset((3, 4)), Offset((5, 4)), Offset((7, 4)))
2 (Offset((0, 1)), Offset((1, 4)), Offset((1, 2)), Offset((3, 4)), Offset((1, 1)), Offset((5, 4)), Offset((3, 2)), Offset((7, 4)))
Returns dictionary.
"""
inventory = []
all_offsets = set()
all_offsets.add(_duration.Offset(self.numerator, self.denominator))
for depth, nodes in sorted(self.root_node._depthwise_inventory().items()):
for node in nodes:
all_offsets.add(_duration.Offset(node.start_offset))
inventory.append(tuple(sorted(all_offsets)))
return tuple(inventory)
@property
def duration(self):
"""
Gets duration of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> meter.duration
Duration(7, 4)
Returns duration.
"""
return _duration.Duration(self.numerator, self.denominator)
@property
def fraction_string(self):
"""
Gets fraction string.
"""
return f"{self.pair[0]}/{self.pair[1]}"
@property
def implied_time_signature(self):
"""
Gets implied time signature of meter.
.. container:: example
>>> abjad.Meter((4, 4)).implied_time_signature
TimeSignature(pair=(4, 4), hide=False, partial=None)
Returns time signature.
"""
duration = self.root_node.preprolated_duration
if hasattr(duration, "pair"):
pair = duration.pair
else:
pair = duration
return _indicators.TimeSignature(pair)
@property
def increase_monotonic(self) -> bool | None:
"""
Is true when meter divides large primes into collections of ``2``
and ``3`` that increase monotonically.
.. container:: example
An asymmetric meter with beats arranged greatest to least:
>>> meter = abjad.Meter(
... (7, 4),
... increase_monotonic=False,
... )
>>> meter.increase_monotonic
False
>>> print(meter.pretty_rtm_format)
(7/4 (
(3/4 (
1/4
1/4
1/4))
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))))
This is default beahvior.
.. container:: example
The same asymmetric meter with unequal beats arranged least to
greatest:
>>> meter = abjad.Meter(
... (7, 4),
... increase_monotonic=True
... )
>>> meter.increase_monotonic
True
>>> print(meter.pretty_rtm_format)
(7/4 (
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))
(3/4 (
1/4
1/4
1/4))))
"""
return self._increase_monotonic
@property
def is_compound(self):
"""
Is true when meter is compound.
.. container:: example
Compound meters written over ``4``:
>>> for numerator in range(1, 13):
... meter = abjad.Meter((numerator, 4))
... string = True if meter.is_compound else ''
... print(str(meter.fraction_string), string)
...
1/4
2/4
3/4
4/4
5/4
6/4 True
7/4
8/4
9/4 True
10/4
11/4
12/4 True
.. container:: example
Compound meters written over ``8``:
>>> for numerator in range(1, 13):
... meter = abjad.Meter((numerator, 8))
... string = True if meter.is_compound else ''
... print(str(meter.fraction_string), string)
...
1/8
2/8
3/8
4/8
5/8
6/8 True
7/8
8/8
9/8 True
10/8
11/8
12/8 True
Compound meters defined equal to those meters with a numerator divisible by ``3``
(but not equal to ``3``).
Returns true or false.
"""
if 3 in _math.divisors(self.numerator):
if not self.numerator == 3:
return True
return False
@property
def is_simple(self):
"""
Is true when meter is simple.
.. container:: example
Simple meters written over ``4``:
>>> for numerator in range(1, 13):
... meter = abjad.Meter((numerator, 4))
... string = True if meter.is_simple else ''
... print(str(meter.fraction_string), string)
...
1/4 True
2/4 True
3/4 True
4/4 True
5/4 True
6/4
7/4 True
8/4 True
9/4
10/4 True
11/4 True
12/4
.. container:: example
Simple meters written over ``8``:
>>> for numerator in range(1, 13):
... meter = abjad.Meter((numerator, 8))
... string = True if meter.is_simple else ''
... print(str(meter.fraction_string), string)
...
1/8 True
2/8 True
3/8 True
4/8 True
5/8 True
6/8
7/8 True
8/8 True
9/8
10/8 True
11/8 True
12/8
Simple meters defined equal to those meters with a numerator not divisible by
``3``.
Meters with numerator equal to ``3`` are also defined as simple.
Returns true or false.
"""
return not self.is_compound
@property
def numerator(self):
"""
Gets numerator of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> meter.numerator
7
Returns positive integer.
"""
return self._numerator
@property
def pair(self):
"""
Gets pair of numerator and denominator of meter.
.. container:: example
>>> meter = abjad.Meter((6, 4))
>>> meter.pair
(6, 4)
Returns pair.
"""
return (self.numerator, self.denominator)
@property
def preferred_boundary_depth(self):
"""
Gets preferred boundary depth of meter.
.. container:: example
No preferred boundary depth:
>>> abjad.Meter((6, 8)).preferred_boundary_depth is None
True
.. container:: example
Customized preferred boundary depth:
>>> meter = abjad.Meter(
... (6, 8),
... preferred_boundary_depth=1,
... )
>>> meter.preferred_boundary_depth
1
Used by ``abjad.Meter.rewrite_meter()``.
Defaults to none.
Set to integer or none.
Returns integer or none.
"""
return self._preferred_boundary_depth
@property
def pretty_rtm_format(self):
"""
Gets pretty RTM format of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> print(meter.pretty_rtm_format)
(7/4 (
(3/4 (
1/4
1/4
1/4))
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))))
Returns string.
"""
return self.root_node.pretty_rtm_format
@property
def root_node(self):
"""
Gets root node of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> for _ in meter.root_node: _
RhythmTreeContainer((3, 4))
RhythmTreeContainer((2, 4))
RhythmTreeContainer((2, 4))
Returns rhythm tree node.
"""
return self._root_node
@property
def rtm_format(self):
"""
Gets RTM format of meter.
.. container:: example
>>> meter = abjad.Meter((7, 4))
>>> meter.rtm_format
'(7/4 ((3/4 (1/4 1/4 1/4)) (2/4 (1/4 1/4)) (2/4 (1/4 1/4))))'
Returns string.
"""
return self._root_node.rtm_format
### PUBLIC METHODS ###
[docs] @staticmethod
def fit_meters(
argument,
meters,
denominator=32,
discard_final_orphan_downbeat=True,
maximum_run_length=None,
starting_offset=None,
):
"""
Finds the best-matching sequence of meters for the offsets contained in
``argument``.
.. container:: example
>>> meters = [(3, 4), (4, 4), (5, 4)]
>>> meters = [abjad.Meter(_) for _ in meters]
.. container:: example
Matches a series of hypothetical ``4/4`` measures:
>>> argument = [(0, 4), (4, 4), (8, 4), (12, 4), (16, 4)]
>>> for meter in abjad.Meter.fit_meters(argument, meters):
... print(meter.implied_time_signature)
...
TimeSignature(pair=(4, 4), hide=False, partial=None)
TimeSignature(pair=(4, 4), hide=False, partial=None)
TimeSignature(pair=(4, 4), hide=False, partial=None)
TimeSignature(pair=(4, 4), hide=False, partial=None)
.. container:: example
Matches a series of hypothetical ``5/4`` measures:
>>> argument = [(0, 4), (3, 4), (5, 4), (10, 4), (15, 4), (20, 4)]
>>> for meter in abjad.Meter.fit_meters(argument, meters):
... print(meter.implied_time_signature)
...
TimeSignature(pair=(3, 4), hide=False, partial=None)
TimeSignature(pair=(4, 4), hide=False, partial=None)
TimeSignature(pair=(3, 4), hide=False, partial=None)
TimeSignature(pair=(5, 4), hide=False, partial=None)
TimeSignature(pair=(5, 4), hide=False, partial=None)
Coerces offsets from ``argument`` via ``MetricAccentKernel.count_offsets()``.
Returns list.
"""
session = _MeterFittingSession(
kernel_denominator=denominator,
maximum_run_length=maximum_run_length,
meters=meters,
offset_counter=argument,
)
meters = session()
return meters
[docs] def generate_offset_kernel_to_denominator(self, denominator, normalize=True):
r"""
Generates a dictionary of all offsets in a meter up to ``denominator``.
Keys are the offsets and the values are the normalized weights of those offsets.
.. container:: example
>>> meter = abjad.Meter((4, 4))
>>> kernel = meter.generate_offset_kernel_to_denominator(8)
>>> for offset, weight in sorted(kernel.kernel.items()):
... print(f"{offset!s}\t{weight!s}")
...
0 3/16
1/8 1/16
1/4 1/8
3/8 1/16
1/2 1/8
5/8 1/16
3/4 1/8
7/8 1/16
1 3/16
This is useful for testing how strongly a collection of offsets responds to a
given meter.
Returns dictionary.
"""
assert _math.is_positive_integer_power_of_two(denominator // self.denominator)
inventory = list(self.depthwise_offset_inventory)
old_flag_count = _duration.Duration(1, self.denominator).flag_count
new_flag_count = _duration.Duration(1, denominator).flag_count
extra_depth = new_flag_count - old_flag_count
for _ in range(extra_depth):
old_offsets = inventory[-1]
new_offsets = []
for first, second in _sequence.nwise(old_offsets):
new_offsets.append(first)
new_offsets.append((first + second) / 2)
new_offsets.append(old_offsets[-1])
inventory.append(tuple(new_offsets))
total = 0
kernel = {}
for offsets in inventory:
for offset in offsets:
if offset not in kernel:
kernel[offset] = 0
kernel[offset] += 1
total += 1
if normalize:
for offset, response in kernel.items():
kernel[offset] = fractions.Fraction(response, total)
return MetricAccentKernel(kernel)
[docs] @staticmethod
def rewrite_meter(
components,
meter,
boundary_depth=None,
initial_offset=None,
maximum_dot_count=None,
rewrite_tuplets=True,
):
r"""
Rewrites ``components`` according to ``meter``.
.. container:: example
Rewrites the contents of a measure in a staff using the default meter for
that measure's time signature:
>>> lily_string = "| 2/4 c'2 ~ |"
>>> lily_string += "| 4/4 c'32 d'2.. ~ d'16 e'32 ~ |"
>>> lily_string += "| 2/4 e'2 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff[:] = container
>>> score = abjad.Score([staff], name="Score")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 2/4
c'2
~
}
{
\time 4/4
c'32
d'2..
~
d'16
e'32
~
}
{
\time 2/4
e'2
}
}
>>> meter = abjad.Meter((4, 4))
>>> print(meter.pretty_rtm_format)
(4/4 (
1/4
1/4
1/4
1/4))
>>> abjad.Meter.rewrite_meter(staff[1][:], meter)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 2/4
c'2
~
}
{
\time 4/4
c'32
d'8..
~
d'2
~
d'8..
e'32
~
}
{
\time 2/4
e'2
}
}
.. container:: example
Rewrites the contents of a measure in a staff using a custom meter:
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff[:] = container
>>> score = abjad.Score([staff], name="Score")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 2/4
c'2
~
}
{
\time 4/4
c'32
d'2..
~
d'16
e'32
~
}
{
\time 2/4
e'2
}
}
>>> rtm = '(4/4 ((2/4 (1/4 1/4)) (2/4 (1/4 1/4))))'
>>> meter = abjad.Meter(rtm)
>>> print(meter.pretty_rtm_format) # doctest: +SKIP
(4/4 (
(2/4 (
1/4
1/4))
(2/4 (
1/4
1/4))))
>>> abjad.Meter.rewrite_meter(staff[1][:], meter)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 2/4
c'2
~
}
{
\time 4/4
c'32
d'4...
~
d'4...
e'32
~
}
{
\time 2/4
e'2
}
}
.. container:: example
Limit the maximum number of dots per leaf using ``maximum_dot_count``:
>>> lily_string = "| 3/4 c'32 d'8 e'8 fs'4... |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 3/4
c'32
d'8
e'8
fs'4...
}
}
Without constraining the ``maximum_dot_count``:
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(measure[:], time_signature)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 3/4
c'32
d'16.
~
d'32
e'16.
~
e'32
fs'4...
}
}
Constraining the ``maximum_dot_count`` to ``2``:
>>> lily_string = "| 3/4 c'32 d'8 e'8 fs'4... |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... maximum_dot_count=2,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 3/4
c'32
d'16.
~
d'32
e'16.
~
e'32
fs'8..
~
fs'4
}
}
Constraining the ``maximum_dot_count`` to ``1``:
>>> lily_string = "| 3/4 c'32 d'8 e'8 fs'4... |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... maximum_dot_count=1,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 3/4
c'32
d'16.
~
d'32
e'16.
~
e'32
fs'16.
~
fs'8
~
fs'4
}
}
Constraining the ``maximum_dot_count`` to ``0``:
>>> lily_string = "| 3/4 c'32 d'8 e'8 fs'4... |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... maximum_dot_count=0,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 3/4
c'32
d'16
~
d'32
~
d'32
e'16
~
e'32
~
e'32
fs'16
~
fs'32
~
fs'8
~
fs'4
}
}
.. container:: example
Split logical ties at different depths of the ``Meter``, if those logical
ties cross any offsets at that depth, but do not also both begin and end at
any of those offsets.
Consider the default meter for ``9/8``:
>>> meter = abjad.Meter((9, 8))
>>> print(meter.pretty_rtm_format)
(9/8 (
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))
(3/8 (
1/8
1/8
1/8))))
We can establish that meter without specifying a ``boundary_depth``:
>>> lily_string = "| 9/8 c'2 d'2 e'8 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 9/8
c'2
d'2
e'8
}
}
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(measure[:], time_signature)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 9/8
c'2
d'4
~
d'4
e'8
}
}
With a ``boundary_depth`` of ``1`` logical ties which cross any offsets
created by nodes with a depth of ``1`` in this Meter's rhythm tree - i.e.
``0/8`` ``3/8`` ``6/8`` and ``9/8`` - which do not also begin and end at any
of those offsets, will be split:
>>> lily_string = "| 9/8 c'2 d'2 e'8 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... boundary_depth=1,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 9/8
c'4.
~
c'8
d'4
~
d'4
e'8
}
}
For this ``9/8`` meter, and this input notation, A ``boundary_depth`` of
``2`` causes no change, as all logical ties already align to multiples of
``1/8``
>>> lily_string = "| 9/8 c'2 d'2 e'8 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... boundary_depth=2,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 9/8
c'2
d'4
~
d'4
e'8
}
}
.. container:: example
Comparison of ``3/4`` and ``6/8`` at ``boundary_depths`` of 0 and 1:
>>> triple = "| 3/4 2 4 || 3/4 4 2 || 3/4 4. 4. |"
>>> triple += "| 3/4 2 ~ 8 8 || 3/4 8 8 ~ 2 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(triple)
>>> staff_1 = abjad.Staff()
>>> staff_1[:] = container
>>> duples = "| 6/8 2 4 || 6/8 4 2 || 6/8 4. 4. |"
>>> duples += "| 6/8 2 ~ 8 8 || 6/8 8 8 ~ 2 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(duples)
>>> staff_2 = abjad.Staff()
>>> staff_2[:] = container
>>> score = abjad.Score([staff_1, staff_2])
In order to see the different time signatures on each staff, we need to move
some engravers from the Score context to the Staff context:
>>> engravers = [
... 'Timing_translator',
... 'Time_signature_engraver',
... 'Default_bar_line_engraver',
... ]
>>> score.remove_commands.extend(engravers)
>>> score[0].consists_commands.extend(engravers)
>>> score[1].consists_commands.extend(engravers)
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
\with
{
\remove Timing_translator
\remove Time_signature_engraver
\remove Default_bar_line_engraver
}
<<
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 3/4
c'2
c'4
}
{
\time 3/4
c'4
c'2
}
{
\time 3/4
c'4.
c'4.
}
{
\time 3/4
c'2
~
c'8
c'8
}
{
\time 3/4
c'8
c'8
~
c'2
}
}
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 6/8
c'2
c'4
}
{
\time 6/8
c'4
c'2
}
{
\time 6/8
c'4.
c'4.
}
{
\time 6/8
c'2
~
c'8
c'8
}
{
\time 6/8
c'8
c'8
~
c'2
}
}
>>
Here we establish a meter without specifying any boundary depth:
>>> for staff in score:
... for container in staff:
... leaf = abjad.get.leaf(container, 0)
... time_signature = abjad.get.indicator(
... leaf,
... abjad.TimeSignature
... )
... abjad.Meter.rewrite_meter(container[:], time_signature)
...
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
\with
{
\remove Timing_translator
\remove Time_signature_engraver
\remove Default_bar_line_engraver
}
<<
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 3/4
c'2
c'4
}
{
\time 3/4
c'4
c'2
}
{
\time 3/4
c'4.
c'4.
}
{
\time 3/4
c'2
~
c'8
c'8
}
{
\time 3/4
c'8
c'8
~
c'2
}
}
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 6/8
c'2
c'4
}
{
\time 6/8
c'4
c'2
}
{
\time 6/8
c'4.
c'4.
}
{
\time 6/8
c'4.
~
c'4
c'8
}
{
\time 6/8
c'8
c'4
~
c'4.
}
}
>>
Here we reestablish meter at a boundary depth of ``1``
>>> for staff in score:
... for container in staff:
... leaf = abjad.get.leaf(container, 0)
... time_signature = abjad.get.indicator(
... leaf,
... abjad.TimeSignature
... )
... abjad.Meter.rewrite_meter(
... container[:],
... time_signature,
... boundary_depth=1,
... )
...
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
\with
{
\remove Timing_translator
\remove Time_signature_engraver
\remove Default_bar_line_engraver
}
<<
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 3/4
c'2
c'4
}
{
\time 3/4
c'4
c'2
}
{
\time 3/4
c'4
~
c'8
c'8
~
c'4
}
{
\time 3/4
c'2
~
c'8
c'8
}
{
\time 3/4
c'8
c'8
~
c'2
}
}
\new Staff
\with
{
\consists Timing_translator
\consists Time_signature_engraver
\consists Default_bar_line_engraver
}
{
{
\time 6/8
c'4.
~
c'8
c'4
}
{
\time 6/8
c'4
c'8
~
c'4.
}
{
\time 6/8
c'4.
c'4.
}
{
\time 6/8
c'4.
~
c'4
c'8
}
{
\time 6/8
c'8
c'4
~
c'4.
}
}
>>
Note that the two time signatures are much more clearly disambiguated above.
.. container:: example
Establishing meter recursively in measures with nested tuplets:
>>> lily_string = "| 4/4 c'16 ~ c'4 d'8. ~ "
>>> lily_string += "2/3 { d'8. ~ 3/5 { d'16 e'8. f'16 ~ } } "
>>> lily_string += "f'4 |"
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(lily_string)
>>> staff = abjad.Staff()
>>> staff.append(container)
>>> score = abjad.Score([staff], name="Score")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 4/4
c'16
~
c'4
d'8.
~
\times 2/3 {
d'8.
~
\tweak text #tuplet-number::calc-fraction-text
\times 3/5 {
d'16
e'8.
f'16
~
}
}
f'4
}
}
When establishing a meter on a selection of components which contain
containers, like tuplets or containers, ``rewrite_meter()`` will recurse into
those containers, treating them as measures whose time signature is derived
from the preprolated preprolated_duration of the container's contents:
>>> measure = staff[0]
>>> time_signature = abjad.get.indicator(
... measure[0],
... abjad.TimeSignature
... )
>>> abjad.Meter.rewrite_meter(
... measure[:],
... time_signature,
... boundary_depth=1,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 4/4
c'4
~
c'16
d'8.
~
\times 2/3 {
d'8
~
d'16
~
\tweak text #tuplet-number::calc-fraction-text
\times 3/5 {
d'16
e'8
~
e'16
f'16
~
}
}
f'4
}
}
.. container:: example
Default rewrite behavior doesn't subdivide the first note in this measure
because the first note in the measure starts at the beginning of a level-0
beat in meter:
>>> staff = abjad.Staff("c'4.. c'16 ~ c'4")
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.TimeSignature((6, 8)), staff[0])
>>> meter = abjad.Meter((6, 8))
>>> abjad.Meter.rewrite_meter(staff[:], meter)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/8
c'4..
c'16
~
c'4
}
Setting boundary depth to 1 subdivides the first note in this measure:
>>> staff = abjad.Staff("c'4.. c'16 ~ c'4")
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.TimeSignature((6, 8)), staff[0])
>>> meter = abjad.Meter((6, 8))
>>> abjad.Meter.rewrite_meter(staff[:], meter, boundary_depth=1)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/8
c'4.
~
c'16
c'16
~
c'4
}
Another way of doing this is by setting preferred boundary depth on the meter
itself:
>>> staff = abjad.Staff("c'4.. c'16 ~ c'4")
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.TimeSignature((6, 8)), staff[0])
>>> meter = abjad.Meter((6, 8), preferred_boundary_depth=1)
>>> abjad.Meter.rewrite_meter(staff[:], meter)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/8
c'4.
~
c'16
c'16
~
c'4
}
This makes it possible to divide different meters in different ways.
.. container:: example
Rewrites notes and tuplets:
>>> lily_string = r"c'8 ~ c'8 ~ c'8 \times 6/7 { c'4. r16 }"
>>> lily_string += r" \times 6/7 { r16 c'4. } c'8 ~ c'8 ~ c'8"
>>> staff = abjad.Staff(lily_string)
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.TimeSignature((6, 4)), staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/4
c'8
~
c'8
~
c'8
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
c'4.
r16
}
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
r16
c'4.
}
c'8
~
c'8
~
c'8
}
>>> meter = abjad.Meter((6, 4))
>>> abjad.Meter.rewrite_meter(
... staff[:],
... meter,
... boundary_depth=1,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/4
c'4.
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
c'8.
~
c'8
~
c'16
r16
}
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
r16
c'8
~
c'4
}
c'4.
}
The tied note rewriting is good while the tuplet rewriting could use some
adjustment.
Rewrites notes but not tuplets:
>>> lily_string = r"c'8 ~ c'8 ~ c'8 \times 6/7 { c'4. r16 }"
>>> lily_string += r" \times 6/7 { r16 c'4. } c'8 ~ c'8 ~ c'8"
>>> staff = abjad.Staff(lily_string)
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.TimeSignature((6, 4)), staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/4
c'8
~
c'8
~
c'8
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
c'4.
r16
}
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
r16
c'4.
}
c'8
~
c'8
~
c'8
}
>>> meter = abjad.Meter((6, 4))
>>> abjad.Meter.rewrite_meter(
... staff[:],
... meter,
... boundary_depth=1,
... rewrite_tuplets=False,
... )
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 6/4
c'4.
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
c'4.
r16
}
\tweak text #tuplet-number::calc-fraction-text
\times 6/7 {
r16
c'4.
}
c'4.
}
Operates in place and returns none.
"""
def recurse(
boundary_depth=None,
boundary_offsets=None,
depth=0,
logical_tie=None,
):
offsets = _MeterManager.get_offsets_at_depth(depth, offset_inventory)
logical_tie_duration = sum(
_._get_preprolated_duration() for _ in logical_tie
)
logical_tie_timespan = _getlib._get_timespan(logical_tie)
logical_tie_start_offset = logical_tie_timespan.start_offset
logical_tie_stop_offset = logical_tie_timespan.stop_offset
logical_tie_starts_in_offsets = logical_tie_start_offset in offsets
logical_tie_stops_in_offsets = logical_tie_stop_offset in offsets
if not _MeterManager.is_acceptable_logical_tie(
logical_tie_duration=logical_tie_duration,
logical_tie_starts_in_offsets=logical_tie_starts_in_offsets,
logical_tie_stops_in_offsets=logical_tie_stops_in_offsets,
maximum_dot_count=maximum_dot_count,
):
split_offset = None
offsets = _MeterManager.get_offsets_at_depth(depth, offset_inventory)
# If the logical tie's start aligns,
# take the latest possible offset.
if logical_tie_starts_in_offsets:
offsets = reversed(offsets)
for offset in offsets:
if logical_tie_start_offset < offset < logical_tie_stop_offset:
split_offset = offset
break
if split_offset is not None:
split_offset -= logical_tie_start_offset
shards = _mutate.split(logical_tie[:], [split_offset])
logical_ties = [_select.LogicalTie(_) for _ in shards]
for logical_tie in logical_ties:
recurse(
boundary_depth=boundary_depth,
boundary_offsets=boundary_offsets,
depth=depth,
logical_tie=logical_tie,
)
else:
recurse(
boundary_depth=boundary_depth,
boundary_offsets=boundary_offsets,
depth=depth + 1,
logical_tie=logical_tie,
)
elif _MeterManager.is_boundary_crossing_logical_tie(
boundary_depth=boundary_depth,
boundary_offsets=boundary_offsets,
logical_tie_start_offset=logical_tie_start_offset,
logical_tie_stop_offset=logical_tie_stop_offset,
):
offsets = boundary_offsets
if logical_tie_start_offset in boundary_offsets:
offsets = reversed(boundary_offsets)
split_offset = None
for offset in offsets:
if logical_tie_start_offset < offset < logical_tie_stop_offset:
split_offset = offset
break
assert split_offset is not None
split_offset -= logical_tie_start_offset
shards = _mutate.split(logical_tie[:], [split_offset])
logical_ties = [_select.LogicalTie(shard) for shard in shards]
for logical_tie in logical_ties:
recurse(
boundary_depth=boundary_depth,
boundary_offsets=boundary_offsets,
depth=depth,
logical_tie=logical_tie,
)
else:
_mutate._fuse(logical_tie[:])
if not isinstance(meter, Meter):
meter = Meter(meter)
boundary_depth = boundary_depth or meter.preferred_boundary_depth
if not isinstance(meter, Meter):
meter = Meter(meter)
if boundary_depth is not None:
boundary_depth = int(boundary_depth)
if maximum_dot_count is not None:
maximum_dot_count = int(maximum_dot_count)
assert 0 <= maximum_dot_count
if initial_offset is None:
initial_offset = _duration.Offset(0)
initial_offset = _duration.Offset(initial_offset)
first_start_offset = components[0]._get_timespan().start_offset
last_start_offset = components[-1]._get_timespan().start_offset
difference = last_start_offset - first_start_offset + initial_offset
assert difference < meter.implied_time_signature.duration
# Build offset inventory, adjusted for initial offset and prolation.
first_offset = components[0]._get_timespan().start_offset
first_offset -= initial_offset
if components[0]._parent is None:
prolation = 1
else:
parentage = _parentage.Parentage(components[0]._parent)
prolation = parentage.prolation
offset_inventory = []
for offsets in meter.depthwise_offset_inventory:
offsets = [(_ * prolation) + first_offset for _ in offsets]
offset_inventory.append(tuple(offsets))
# Build boundary offset inventory, if applicable.
if boundary_depth is not None:
boundary_offsets = offset_inventory[boundary_depth]
else:
boundary_offsets = None
# Cache results of iterator;
# we'll be mutating the underlying collection
iterator = _MeterManager.iterate_rewrite_inputs(components)
items = tuple(iterator)
for item in items:
if isinstance(item, _select.LogicalTie):
recurse(
boundary_depth=boundary_depth,
boundary_offsets=boundary_offsets,
depth=0,
logical_tie=item,
)
elif isinstance(item, _score.Tuplet) and not rewrite_tuplets:
pass
else:
preprolated_duration = sum(
[_._get_preprolated_duration() for _ in item]
)
if preprolated_duration.numerator == 1:
pair = _duration.with_denominator(
preprolated_duration, 4 * preprolated_duration.denominator
)
preprolated_duration = pair
sub_metrical_hierarchy = Meter(preprolated_duration)
sub_boundary_depth = 1
if boundary_depth is None:
sub_boundary_depth = None
Meter.rewrite_meter(
item[:],
sub_metrical_hierarchy,
boundary_depth=sub_boundary_depth,
maximum_dot_count=maximum_dot_count,
)
[docs]def illustrate_meter_list(
meter_list, denominator=16, range_=None, scale=None
) -> _lilypondfile.LilyPondFile:
r"""
Illustrates meters.
.. container:: example
>>> meters = [abjad.Meter(_) for _ in [(3, 4), (5, 16), (7, 8)]]
>>> lilypond_file = abjad.meter.illustrate_meter_list(meters, scale=0.5)
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> lilypond_file = abjad.meter.illustrate_meter_list(meters)
>>> markup = lilypond_file.items[0]
>>> string = abjad.lilypond(markup)
>>> print(string)
\markup
\column
{
\combine
\combine
\translate #'(1.0 . 1)
\sans \fontsize #-3 \center-align \fraction 3 4
\translate #'(49.38709677419355 . 1)
\sans \fontsize #-3 \center-align \fraction 5 16
\translate #'(69.54838709677419 . 1)
\sans \fontsize #-3 \center-align \fraction 7 8
\combine
\postscript
#"
0.2 setlinewidth
1 0.5 moveto
49.38709677419355 0.5 lineto
stroke
1 1.25 moveto
1 -0.25 lineto
stroke
49.38709677419355 1.25 moveto
49.38709677419355 -0.25 lineto
stroke
49.38709677419355 0.5 moveto
69.54838709677419 0.5 lineto
stroke
49.38709677419355 1.25 moveto
49.38709677419355 -0.25 lineto
stroke
69.54838709677419 1.25 moveto
69.54838709677419 -0.25 lineto
stroke
69.54838709677419 0.5 moveto
126 0.5 lineto
stroke
69.54838709677419 1.25 moveto
69.54838709677419 -0.25 lineto
stroke
126 1.25 moveto
126 -0.25 lineto
stroke
"
\postscript
#"
1 -2 moveto
0 -6.153846153846154 rlineto
stroke
5.032258064516129 -2 moveto
0 -1.5384615384615385 rlineto
stroke
9.064516129032258 -2 moveto
0 -3.076923076923077 rlineto
stroke
13.096774193548388 -2 moveto
0 -1.5384615384615385 rlineto
stroke
17.129032258064516 -2 moveto
0 -4.615384615384616 rlineto
stroke
21.161290322580644 -2 moveto
0 -1.5384615384615385 rlineto
stroke
25.193548387096776 -2 moveto
0 -3.076923076923077 rlineto
stroke
29.225806451612904 -2 moveto
0 -1.5384615384615385 rlineto
stroke
33.25806451612903 -2 moveto
0 -4.615384615384616 rlineto
stroke
37.29032258064516 -2 moveto
0 -1.5384615384615385 rlineto
stroke
41.32258064516129 -2 moveto
0 -3.076923076923077 rlineto
stroke
45.354838709677416 -2 moveto
0 -1.5384615384615385 rlineto
stroke
49.38709677419355 -2 moveto
0 -6.153846153846154 rlineto
stroke
49.38709677419355 -2 moveto
0 -10.909090909090908 rlineto
stroke
53.41935483870968 -2 moveto
0 -3.6363636363636367 rlineto
stroke
57.45161290322581 -2 moveto
0 -3.6363636363636367 rlineto
stroke
61.483870967741936 -2 moveto
0 -7.272727272727273 rlineto
stroke
65.51612903225806 -2 moveto
0 -3.6363636363636367 rlineto
stroke
69.54838709677419 -2 moveto
0 -10.909090909090908 rlineto
stroke
69.54838709677419 -2 moveto
0 -5.517241379310345 rlineto
stroke
73.58064516129032 -2 moveto
0 -1.3793103448275863 rlineto
stroke
77.61290322580645 -2 moveto
0 -2.7586206896551726 rlineto
stroke
81.64516129032258 -2 moveto
0 -1.3793103448275863 rlineto
stroke
85.6774193548387 -2 moveto
0 -2.7586206896551726 rlineto
stroke
89.70967741935483 -2 moveto
0 -1.3793103448275863 rlineto
stroke
93.74193548387096 -2 moveto
0 -4.137931034482759 rlineto
stroke
97.7741935483871 -2 moveto
0 -1.3793103448275863 rlineto
stroke
101.80645161290323 -2 moveto
0 -2.7586206896551726 rlineto
stroke
105.83870967741936 -2 moveto
0 -1.3793103448275863 rlineto
stroke
109.87096774193549 -2 moveto
0 -4.137931034482759 rlineto
stroke
113.90322580645162 -2 moveto
0 -1.3793103448275863 rlineto
stroke
117.93548387096774 -2 moveto
0 -2.7586206896551726 rlineto
stroke
121.96774193548387 -2 moveto
0 -1.3793103448275863 rlineto
stroke
126 -2 moveto
0 -5.517241379310345 rlineto
stroke
"
}
"""
durations = [_.duration for _ in meter_list]
total_duration = sum(durations)
offsets = _math.cumulative_sums(durations, start=0)
timespans = _timespan.TimespanList()
for one, two in _sequence.nwise(offsets):
timespan = _timespan.Timespan(start_offset=one, stop_offset=two)
timespans.append(timespan)
if range_ is not None:
minimum, maximum = range_
else:
minimum, maximum = 0, total_duration
minimum = float(_duration.Offset(minimum))
maximum = float(_duration.Offset(maximum))
if scale is None:
scale = 1.0
assert 0 < scale
postscript_scale = 125.0 / (maximum - minimum)
postscript_scale *= float(scale)
postscript_x_offset = (minimum * postscript_scale) - 1
timespan_markup = _timespan._make_timespan_list_markup(
timespans,
postscript_x_offset,
postscript_scale,
draw_offsets=False,
)
postscript_strings = []
rational_x_offset = _duration.Offset(0)
for meter in meter_list:
kernel_denominator = denominator or meter.denominator
kernel = MetricAccentKernel.from_meter(meter, kernel_denominator)
for offset, weight in sorted(kernel.kernel.items()):
weight = float(weight) * -40
ps_x_offset = float(rational_x_offset + offset)
ps_x_offset *= postscript_scale
ps_x_offset += 1
postscript_strings.append(f"{_timespan._fpa(ps_x_offset)} -2 moveto")
postscript_strings.append(f"0 {_timespan._fpa(weight)} rlineto")
postscript_strings.append("stroke")
rational_x_offset += meter.duration
fraction_pairs = []
for meter, offset in zip(meter_list, offsets):
numerator, denominator = meter.numerator, meter.denominator
x_translation = float(offset) * postscript_scale
x_translation -= postscript_x_offset
top_string = rf"\translate #'({x_translation} . 1)"
bottom_string = r"\sans \fontsize #-3 \center-align"
bottom_string = bottom_string + rf" \fraction {numerator} {denominator}"
pair = (top_string, bottom_string)
fraction_pairs.append(pair)
fraction_strings = []
fraction_strings.append(fraction_pairs[0][0])
fraction_strings.append(fraction_pairs[0][1])
for pair in fraction_pairs[1:]:
fraction_strings.insert(0, r"\combine")
fraction_strings.append(pair[0])
fraction_strings.append(pair[1])
strings = []
strings.append(r"\markup")
strings.append(r"\column")
strings.append("{")
strings.extend(fraction_strings)
strings.append(r"\combine")
strings.append(timespan_markup.string)
strings.append(r"\postscript")
strings.append('#"')
strings.extend(postscript_strings)
strings.append('"')
strings.append("}")
string = "\n".join(strings)
markup = _indicators.Markup(string)
lilypond_file = _lilypondfile.LilyPondFile()
lilypond_file.items.append(markup)
return lilypond_file
[docs]class MetricAccentKernel:
"""
Metric accent kernel.
.. container:: example
>>> hierarchy = abjad.Meter((7, 8))
>>> kernel = hierarchy.generate_offset_kernel_to_denominator(8)
>>> kernel
MetricAccentKernel(kernel={Offset((0, 1)): Fraction(3, 14), Offset((7, 8)): Fraction(3, 14), Offset((3, 8)): Fraction(1, 7), Offset((5, 8)): Fraction(1, 7), Offset((1, 8)): Fraction(1, 14), Offset((1, 4)): Fraction(1, 14), Offset((1, 2)): Fraction(1, 14), Offset((3, 4)): Fraction(1, 14)})
Call the kernel against an expression from which offsets can be counted to receive an
impulse-response:
.. container:: example
>>> offsets = [(0, 8), (1, 8), (1, 8), (3, 8)]
>>> kernel(offsets)
Fraction(1, 2)
"""
### CLASS VARIABLES ###
__slots__ = ("_kernel", "_offsets")
### INITIALIZER ###
def __init__(self, kernel=None):
kernel = kernel or {}
assert isinstance(kernel, dict)
for key, value in kernel.items():
assert isinstance(key, _duration.Offset)
assert isinstance(value, fractions.Fraction)
self._kernel = kernel.copy()
self._offsets = tuple(sorted(self._kernel))
### SPECIAL METHODS ###
[docs] def __call__(self, argument):
r"""
Calls metrical accent kernal on ``argument``.
>>> upper_staff = abjad.Staff("c'8 d'4. e'8 f'4.")
>>> lower_staff = abjad.Staff(r'\clef bass c4 b,4 a,2')
>>> score = abjad.Score([upper_staff, lower_staff])
>>> kernel = abjad.MetricAccentKernel.from_meter((4, 4))
>>> kernel(score)
Fraction(10, 33)
Returns float.
"""
offset_count = self.count_offsets(argument)
response = fractions.Fraction(0, 1)
for offset, count in offset_count.items.items():
if offset in self._kernel:
weight = self._kernel[offset]
weighted_count = weight * count
response += weighted_count
return response
[docs] def __eq__(self, argument):
"""
Is true when ``argument`` is a metrical accent kernal with a kernal equal to that
of this metrical accent kernel.
Returns true or false.
"""
if isinstance(argument, type(self)):
if self.kernel == argument.kernel:
if self.duration == argument.duration:
return True
return False
[docs] def __hash__(self):
"""
Hashes metric accent kernel.
Returns integer.
"""
return super().__hash__()
[docs] def __repr__(self) -> str:
"""
Gets repr.
"""
return f"{type(self).__name__}(kernel={self.kernel})"
### PUBLIC PROPERTIES ###
@property
def duration(self):
"""
Gets duration.
"""
if self._offsets:
return _duration.Duration(self._offsets[-1])
else:
return _duration.Duration(0)
@property
def kernel(self):
"""
The kernel datastructure.
Returns dict.
"""
return self._kernel.copy()
### PUBLIC METHODS ###
[docs] @staticmethod
def count_offsets(argument) -> _timespan.OffsetCounter:
r"""
Count offsets in ``argument``.
.. container:: example
>>> upper_staff = abjad.Staff("c'8 d'4. e'8 f'4.")
>>> lower_staff = abjad.Staff(r'\clef bass c4 b,4 a,2')
>>> score = abjad.Score([upper_staff, lower_staff])
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
c'8
d'4.
e'8
f'4.
}
\new Staff
{
\clef "bass"
c4
b,4
a,2
}
>>
>>> abjad.show(score) # doctest: +SKIP
>>> MetricAccentKernel = abjad.MetricAccentKernel
>>> leaves = abjad.select.leaves(score)
>>> counter = abjad.MetricAccentKernel.count_offsets(leaves)
>>> for offset, count in sorted(counter.items.items()):
... offset, count
(Offset((0, 1)), 2)
(Offset((1, 8)), 2)
(Offset((1, 4)), 2)
(Offset((1, 2)), 4)
(Offset((5, 8)), 2)
(Offset((1, 1)), 2)
.. container:: example
>>> a = abjad.Timespan(0, 10)
>>> b = abjad.Timespan(5, 15)
>>> c = abjad.Timespan(15, 20)
>>> counter = MetricAccentKernel.count_offsets((a, b, c))
>>> for offset, count in sorted(counter.items.items()):
... offset, count
(Offset((0, 1)), 1)
(Offset((5, 1)), 1)
(Offset((10, 1)), 1)
(Offset((15, 1)), 2)
(Offset((20, 1)), 1)
Returns counter.
"""
return _timespan.OffsetCounter(argument)
[docs] @staticmethod
def from_meter(meter, denominator=32, normalize=True):
"""
Create a metric accent kernel from ``meter``.
Returns new metric accent kernel.
"""
if not isinstance(meter, Meter):
meter = Meter(meter)
return meter.generate_offset_kernel_to_denominator(
denominator=denominator, normalize=normalize
)
class _MeterFittingSession:
"""
Meter-fitting session.
Used internally by Meter.fit_meters().
"""
### CLASS VARIABLES ###
__slots__ = (
"_cached_offset_counters",
"_kernel_denominator",
"_kernels",
"_longest_kernel",
"_maximum_run_length",
"_meters",
"_offset_counter",
"_ordered_offsets",
)
KernelScore = collections.namedtuple("KernelScore", ("kernel", "score"))
### INITIALIZER ###
def __init__(
self,
kernel_denominator=32,
maximum_run_length=None,
meters=None,
offset_counter=None,
):
self._cached_offset_counters = {}
if maximum_run_length is not None:
maximum_run_length = int(maximum_run_length)
assert 0 < maximum_run_length
self._maximum_run_length = maximum_run_length
if offset_counter:
self._offset_counter = MetricAccentKernel.count_offsets(offset_counter)
else:
self._offset_counter = {}
self._ordered_offsets = tuple(sorted(self.offset_counter.items))
meters = meters or ()
self._meters = tuple(Meter(_) for _ in meters)
self._kernel_denominator = _duration.Duration(kernel_denominator)
self._kernels = {}
for meter in self._meters:
kernel = meter.generate_offset_kernel_to_denominator(
self._kernel_denominator
)
self._kernels[kernel] = meter
if self.kernels:
self._longest_kernel = sorted(self._kernels, key=lambda _: _.duration)[-1]
else:
self._longest_kernel = None
### SPECIAL METHODS ###
def __call__(self):
"""
Fits meters.
Returns meter list.
"""
selected_kernels = []
current_offset = _duration.Offset(0)
while current_offset < self.ordered_offsets[-1]:
kernel_scores = []
kernels = self._get_kernels(selected_kernels)
offset_counter = self._get_offset_counter_at(current_offset)
if not offset_counter:
winning_kernel = self.longest_kernel
if selected_kernels:
winning_kernel = selected_kernels[-1]
else:
for kernel in kernels:
if (
self.maximum_run_length
and 1 < len(kernels)
and self.maximum_run_length <= len(selected_kernels)
):
last_n_kernels = selected_kernels[-self.maximum_run_length :]
if len(set(last_n_kernels)) == 1:
if kernel == last_n_kernels[-1]:
continue
initial_score = kernel(offset_counter)
lookahead_score = self._get_lookahead_score(
current_offset, kernel, kernels
)
score = initial_score + lookahead_score
kernel_score = self.KernelScore(kernel=kernel, score=score)
kernel_scores.append(kernel_score)
kernel_scores.sort(key=lambda kernel_score: kernel_score.score)
winning_kernel = kernel_scores[-1].kernel
selected_kernels.append(winning_kernel)
current_offset += winning_kernel.duration
selected_meters = (self.kernels[_] for _ in selected_kernels)
return selected_meters
### PRIVATE METHODS ###
def _get_kernels(self, selected_kernels):
return tuple(self.kernels)
def _get_lookahead_score(self, current_offset, kernel, kernels):
lookahead_scores = []
lookahead_offset = current_offset + kernel.duration
lookahead_offset_counter = self._get_offset_counter_at(lookahead_offset)
for lookahead_kernel in kernels:
lookahead_scores.append(lookahead_kernel(lookahead_offset_counter))
lookahead_score = sum(lookahead_scores) # / len(lookahead_scores)
return lookahead_score
def _get_offset_counter_at(self, start_offset):
if start_offset in self.cached_offset_counters:
return self.cached_offset_counters[start_offset]
offset_counter = {}
stop_offset = start_offset + self.longest_kernel.duration
index = bisect.bisect_left(self.ordered_offsets, start_offset)
if index == len(self.ordered_offsets):
return offset_counter
offset = self.ordered_offsets[index]
while offset <= stop_offset:
count = self.offset_counter.items[offset]
offset_counter[offset - start_offset] = count
index += 1
if index == len(self.ordered_offsets):
break
offset = self.ordered_offsets[index]
self.cached_offset_counters[start_offset] = offset_counter
return offset_counter
### PUBLIC PROPERTIES ###
@property
def cached_offset_counters(self):
"""
Gets cached offset counters
Returns dictionary.
"""
return self._cached_offset_counters
@property
def kernel_denominator(self):
"""
Gets kernel denominator.
Returns duration.
"""
return self._kernel_denominator
@property
def kernels(self):
"""
Gets kernels-to-meter dictionary.
Returns dictionary.
"""
return self._kernels
@property
def longest_kernel(self):
"""
Gets longest kernel.
Returns kernel.
"""
return self._longest_kernel
@property
def maximum_run_length(self):
"""
Gets maximum meter repetitions.
Returns integer or none.
"""
return self._maximum_run_length
@property
def meters(self):
"""
Gets meters.
Returns meters.
"""
return self._meters
@property
def offset_counter(self):
"""
Gets offset counter.
Returns offset counter.
"""
return self._offset_counter
@property
def ordered_offsets(self):
"""
Gets ordered offsets.
Returns offsets.
"""
return self._ordered_offsets
class _MeterManager:
"""
Meter manager.
"""
### CLASS VARIABLES ###
__slots__ = ()
### PUBLIC METHODS ###
@staticmethod
def get_offsets_at_depth(depth, offset_inventory):
"""
Gets offsets at ``depth`` in ``offset_inventory``.
"""
if depth < len(offset_inventory):
return offset_inventory[depth]
while len(offset_inventory) <= depth:
new_offsets = []
old_offsets = offset_inventory[-1]
for first, second in _sequence.nwise(old_offsets):
new_offsets.append(first)
difference = second - first
half = (first + second) / 2
if _duration.Duration(1, 8) < difference:
new_offsets.append(half)
else:
one_quarter = (first + half) / 2
three_quarters = (half + second) / 2
new_offsets.append(one_quarter)
new_offsets.append(half)
new_offsets.append(three_quarters)
new_offsets.append(old_offsets[-1])
offset_inventory.append(tuple(new_offsets))
return offset_inventory[depth]
@staticmethod
def is_acceptable_logical_tie(
logical_tie_duration=None,
logical_tie_starts_in_offsets=None,
logical_tie_stops_in_offsets=None,
maximum_dot_count=None,
):
"""
Is true if logical tie is acceptable.
"""
# print '\tTESTING ACCEPTABILITY'
if not logical_tie_duration.is_assignable:
return False
if (
maximum_dot_count is not None
and maximum_dot_count < logical_tie_duration.dot_count
):
return False
if not logical_tie_starts_in_offsets and not logical_tie_stops_in_offsets:
return False
return True
@staticmethod
def is_boundary_crossing_logical_tie(
boundary_depth=None,
boundary_offsets=None,
logical_tie_start_offset=None,
logical_tie_stop_offset=None,
):
"""
Is true if logical tie crosses meter boundaries.
"""
# print '\tTESTING BOUNDARY CROSSINGS'
if boundary_depth is None:
return False
if not any(
logical_tie_start_offset < _ < logical_tie_stop_offset
for _ in boundary_offsets
):
return False
if (
logical_tie_start_offset in boundary_offsets
and logical_tie_stop_offset in boundary_offsets
):
return False
return True
@staticmethod
def iterate_rewrite_inputs(argument):
r"""
Iterates topmost masked logical ties, rest groups and containers in ``argument``,
masked by ``argument``.
>>> string = "! 2/4 c'4 d'4 ~ !"
>>> string += "! 4/4 d'8. r16 r8. e'16 ~ "
>>> string += "2/3 { e'8 ~ e'8 f'8 ~ } f'4 ~ !"
>>> string += "! 4/4 f'8 g'8 ~ g'4 a'4 ~ a'8 b'8 ~ !"
>>> string += "! 2/4 b'4 c''4 !"
>>> string = string.replace('!', '|')
>>> container = abjad.parsers.reduced.parse_reduced_ly_syntax(string)
>>> staff = abjad.Staff()
>>> score = abjad.Score([staff], name="Score")
>>> staff[:] = container
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
{
\time 2/4
c'4
d'4
~
}
{
\time 4/4
d'8.
r16
r8.
e'16
~
\times 2/3 {
e'8
~
e'8
f'8
~
}
f'4
~
}
{
\time 4/4
f'8
g'8
~
g'4
a'4
~
a'8
b'8
~
}
{
\time 2/4
b'4
c''4
}
}
>>> for x in abjad.meter._MeterManager.iterate_rewrite_inputs(
... staff[0]): x
...
LogicalTie(items=[Note("c'4")])
LogicalTie(items=[Note("d'4")])
>>> for x in abjad.meter._MeterManager.iterate_rewrite_inputs(
... staff[1]): x
...
LogicalTie(items=[Note("d'8.")])
LogicalTie(items=[Rest('r16'), Rest('r8.')])
LogicalTie(items=[Note("e'16")])
Tuplet('3:2', "e'8 e'8 f'8")
LogicalTie(items=[Note("f'4")])
>>> for x in abjad.meter._MeterManager.iterate_rewrite_inputs(
... staff[2]): x
...
LogicalTie(items=[Note("f'8")])
LogicalTie(items=[Note("g'8"), Note("g'4")])
LogicalTie(items=[Note("a'4"), Note("a'8")])
LogicalTie(items=[Note("b'8")])
>>> for x in abjad.meter._MeterManager.iterate_rewrite_inputs(
... staff[3]): x
...
LogicalTie(items=[Note("b'4")])
LogicalTie(items=[Note("c''4")])
Returns generator.
"""
last_tie = None
current_leaf_group = None
current_leaf_group_is_silent = False
for component in argument:
if isinstance(component, _score.Note | _score.Chord):
this_tie_leaves = _iterlib._get_logical_tie_leaves(component)
this_tie = _select.LogicalTie(this_tie_leaves)
if current_leaf_group is None:
current_leaf_group = []
elif (
current_leaf_group_is_silent
or this_tie is None
or last_tie != this_tie
):
yield _select.LogicalTie(current_leaf_group)
current_leaf_group = []
current_leaf_group_is_silent = False
current_leaf_group.append(component)
last_tie = this_tie
elif isinstance(component, _score.Rest | _score.Skip):
if current_leaf_group is None:
current_leaf_group = []
elif not current_leaf_group_is_silent:
yield _select.LogicalTie(current_leaf_group)
current_leaf_group = []
current_leaf_group_is_silent = True
current_leaf_group.append(component)
last_tie = None
elif isinstance(component, _score.Container):
if current_leaf_group is not None:
yield _select.LogicalTie(current_leaf_group)
current_leaf_group = None
last_tie = None
yield component
else:
raise Exception(f"unhandled component: {component!r}.")
if current_leaf_group is not None:
yield _select.LogicalTie(current_leaf_group)