import abjad
[docs]def underfull_duration(selection: abjad.Selection) -> abjad.Duration:
r"""Returns an |abjad.Duration| representing the duration missing in the
last measure of an input |abjad.Selection| which is not fully filled in.
Basic usage:
Returns the missing duration of the last measure of an
|abjad.Selection|. If no time signature is encountered, it uses
LilyPond's fallback time signature of ``4/4``.
>>> container1 = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> container2 = abjad.Container(r"c'4 d'4 e'4")
>>> container3 = abjad.Container(r"c'4 d'4 e'4 f'4 | c'4")
>>> container4 = abjad.Container(r"c'4 d'4 e'4 f'4 | c'4 d'4 e'4 f'4")
>>> auxjad.get.underfull_duration(container1[:])
0
>>> auxjad.get.underfull_duration(container2[:])
1/4
>>> auxjad.get.underfull_duration(container3[:])
3/4
>>> auxjad.get.underfull_duration(container4[:])
0
.. note::
Auxjad automatically adds this function as an extension function to
|abjad.get|. It can thus be used from either |auxjad.get|_ or
|abjad.get| namespaces. Therefore, the two lines below are equivalent:
>>> container = abjad.Container(r"c'4 d'4 e'4")
>>> auxjad.get.underfull_duration(container[:])
1/4
>>> abjad.get.underfull_duration(container[:])
1/4
Time signature changes:
Handles any time signatures as well as changes of time signature.
>>> container1 = abjad.Container(r"\time 4/4 c'4 d'4 e'4 f'4")
>>> container2 = abjad.Container(r"\time 3/4 a2. \time 2/4 r2")
>>> container3 = abjad.Container(r"\time 5/4 g1 ~ g4 \time 4/4 af'2")
>>> container4 = abjad.Container(r"\time 6/8 c'2 ~ c'8")
>>> auxjad.get.underfull_duration(container1[:])
0
>>> auxjad.get.underfull_duration(container2[:])
0
>>> auxjad.get.underfull_duration(container3[:])
1/2
>>> auxjad.get.underfull_duration(container4[:])
1/8
Partial time signatures:
Correctly handles partial time signatures.
>>> container = abjad.Container(r"c'4 d'4 e'4 f'4")
>>> time_signature = abjad.TimeSignature((3, 4), partial=(1, 4))
>>> abjad.attach(time_signature, container[0])
>>> auxjad.get.underfull_duration(container[:])
0
Multi-measure rests:
It also handles multi-measure rests.
>>> container1 = abjad.Container(r"R1")
>>> container2 = abjad.Container(r"\time 3/4 R1 * 3/4 \time 2/4 r2")
>>> container3 = abjad.Container(r"\time 5/4 R1 * 5/4 \time 4/4 g''4")
>>> container4 = abjad.Container(r"\time 6/8 R1 * 1/2")
>>> auxjad.get.underfull_duration(container1[:])
0
>>> auxjad.get.underfull_duration(container2[:])
0
>>> auxjad.get.underfull_duration(container3[:])
3/4
>>> auxjad.get.underfull_duration(container4[:])
1/4
.. error::
If a selection is malformed, i.e. it has an underfilled measure before
a time signature change, the function raises a :exc:`ValueError`
exception. This is also the case when a selection starts in the middle
of a measure.
>>> container = abjad.Container(r"\time 5/4 g''1 \time 4/4 f'1")
>>> auxjad.get.underfull_duration(container[:])
ValueError: 'selection' is malformed, with an underfull measure
preceding a time signature change
.. warning::
The input container must be a contiguous logical voice. When dealing
with a container with multiple subcontainers (e.g. a score containing
multiple staves), the best approach is to cycle through these
subcontainers, applying this function to them individually.
"""
if not isinstance(selection, abjad.Selection):
raise TypeError("argument must be 'abjad.Selection'")
if not selection.leaves().are_contiguous_logical_voice():
raise ValueError("argument must be contiguous logical voice")
leaves = selection.leaves()
# handling first leaf
time_signature = abjad.get.effective(leaves[0], abjad.TimeSignature)
if time_signature is not None:
effective_time_signature = time_signature
else:
effective_time_signature = abjad.TimeSignature((4, 4))
duration = abjad.get.duration(leaves[0])
# handling partial time signatures
if effective_time_signature.partial is not None:
duration += effective_time_signature.duration
duration -= effective_time_signature.partial
# all other leaves
for leaf in leaves[1:]:
time_signature = abjad.get.effective(leaf, abjad.TimeSignature)
if (time_signature is not None
and time_signature != effective_time_signature):
if duration % effective_time_signature.duration != 0:
raise ValueError("'selection' is malformed, with an underfull "
"measure preceding a time signature change")
effective_time_signature = time_signature
duration = abjad.Duration(0)
duration += abjad.get.duration(leaf)
duration_last_bar = duration % effective_time_signature.duration
duration_left = duration_last_bar
if duration_last_bar > abjad.Duration(0):
duration_left = effective_time_signature.duration - duration_last_bar
return duration_left