From f2200b7c1345f3ded72631ed82fb0e7eedb4bcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trevor=20Ba=C4=8Da?= Date: Thu, 8 Feb 2024 14:57:35 -0500 Subject: [PATCH] Added abjad.IndependentAfterGraceContainer. Tag #1125. CHANGED. Cleaned up abjad.spanners.glissando() overrides. OLD: abjad.glissando() included spurious blocks like this on length-2 glissandi; these settings did nothing: \hide NoteHead \override Accidental.stencil = ##f \override NoteColumn.glissando-skip = ##t \override NoteHead.no-ledgers = ##t \revert Accidental.stencil \revert NoteColumn.glissando-skip \revert NoteHead.no-ledgers \undo \hide NoteHead NEW: abjad.glissando() no longer includes these blocks on length-2 glissandi. REMOVED. Removed abjad.spanners.glissando(..., style=None) keyword. OLD: abjad.spanners.glissand(..., style="trill") NEW: Use abjad.Tweak(r"- \tweak style #'trill") Makefile update: changed "make pytest" to "pytest tests". Newer versions of pytest (eg, 8.1.1) error on "pytest ." because of duplicate conftest.py, conf.py files in the abjad/abjad and abjad/tests. OLD: "make pytest" defined equal to "pytest ." NEW: "make pytest" defined equal to "pytest tests" --- Makefile | 6 +- abjad/__init__.py | 2 + abjad/_getlib.py | 2 + abjad/_iterlib.py | 4 + abjad/_updatelib.py | 37 +++ abjad/configuration.py | 1 + abjad/get.py | 441 +++++++++++++++++++++++++- abjad/meter.py | 11 +- abjad/mutate.py | 62 ++++ abjad/score.py | 96 +++++- abjad/select.py | 705 ++++++++++++++++++++++++++++++++++++++--- abjad/spanners.py | 3 +- 12 files changed, 1316 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index bf47e5f5ab4..d3fe1965d62 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ mypy: project = abjad pytest: - pytest . + pytest tests pytest-coverage: rm -Rf htmlcov/ @@ -88,10 +88,10 @@ pytest-coverage: --cov-config=.coveragerc \ --cov-report=html \ --cov=${project} \ - . + tests pytest-x: - pytest -x . + pytest -x tests reformat: make black-reformat diff --git a/abjad/__init__.py b/abjad/__init__.py index f485f4fdf6e..252039f25dd 100644 --- a/abjad/__init__.py +++ b/abjad/__init__.py @@ -232,6 +232,7 @@ Container, Context, DrumNoteHead, + IndependentAfterGraceContainer, Leaf, MultimeasureRest, Note, @@ -345,6 +346,7 @@ "Harpsichord", "Horizontal", "ImpreciseMetronomeMarkError", + "IndependentAfterGraceContainer", "Infinity", "Instrument", "InstrumentName", diff --git a/abjad/_getlib.py b/abjad/_getlib.py index a349ab6705d..3575c15c039 100644 --- a/abjad/_getlib.py +++ b/abjad/_getlib.py @@ -177,6 +177,8 @@ def _get_effective( def _get_grace_container(component): + # _score.IndependentAfterGraceContainer is excluded here; + # exclusion allows iteration to work correctly prototype = ( _score.AfterGraceContainer, _score.BeforeGraceContainer, diff --git a/abjad/_iterlib.py b/abjad/_iterlib.py index f7f9b118e6f..da8e77fa03b 100644 --- a/abjad/_iterlib.py +++ b/abjad/_iterlib.py @@ -158,8 +158,12 @@ def _get_sibling_with_graces(component, n): candidate = component._parent[index] if n == 1 and getattr(candidate, "_before_grace_container", None): return candidate._before_grace_container[0] + if n == 1 and candidate.__class__.__name__ == "IndependentAfterGraceContainer": + return candidate[0] if n == -1 and getattr(candidate, "_after_grace_container", None): return candidate._after_grace_container[-1] + if n == -1 and candidate.__class__.__name__ == "IndependentAfterGraceContainer": + return candidate[-1] return candidate diff --git a/abjad/_updatelib.py b/abjad/_updatelib.py index 5972e8e7061..84a8093b726 100644 --- a/abjad/_updatelib.py +++ b/abjad/_updatelib.py @@ -65,6 +65,35 @@ def _get_before_grace_leaf_offsets(leaf): return start_offset, stop_offset +def _get_independent_after_grace_leaf_offsets(leaf): + container = leaf._parent + main_leaf = container._sibling(-1) + main_leaf_stop_offset = main_leaf._stop_offset + assert main_leaf_stop_offset is not None + displacement = -leaf._get_duration() + sibling = leaf._sibling(1) + while sibling is not None and sibling._parent is container: + displacement -= sibling._get_duration() + sibling = sibling._sibling(1) + """ + if leaf._parent is not None and leaf._parent._sibling(-1) is not None: + main_leaf = leaf._parent._sibling(-1) + sibling = main_leaf._sibling(1) + if ( + sibling is not None + and hasattr(sibling, "_before_grace_container") + and sibling._before_grace_container is not None + ): + before_grace_container = sibling._before_grace_container + duration = before_grace_container._get_duration() + displacement -= duration + """ + start_offset = _duration.Offset(main_leaf_stop_offset, displacement=displacement) + displacement += leaf._get_duration() + stop_offset = _duration.Offset(main_leaf_stop_offset, displacement=displacement) + return start_offset, stop_offset + + def _get_measure_start_offsets(component): wrappers = [] prototype = _indicators.TimeSignature @@ -323,9 +352,17 @@ def _update_component_offsets(component): start_offset = pair[0] pair = _get_after_grace_leaf_offsets(component[-1]) stop_offset = pair[-1] + elif isinstance(component, _score.IndependentAfterGraceContainer): + pair = _get_independent_after_grace_leaf_offsets(component[0]) + start_offset = pair[0] + pair = _get_independent_after_grace_leaf_offsets(component[-1]) + stop_offset = pair[-1] elif isinstance(component._parent, _score.AfterGraceContainer): pair = _get_after_grace_leaf_offsets(component) start_offset, stop_offset = pair + elif isinstance(component._parent, _score.IndependentAfterGraceContainer): + pair = _get_independent_after_grace_leaf_offsets(component) + start_offset, stop_offset = pair else: previous = component._sibling(-1) if previous is not None: diff --git a/abjad/configuration.py b/abjad/configuration.py index 6d39fc3e348..60d6fbde5e5 100644 --- a/abjad/configuration.py +++ b/abjad/configuration.py @@ -578,6 +578,7 @@ def list_all_classes(modules="abjad", ignored_classes=None): + diff --git a/abjad/get.py b/abjad/get.py index e36cc79de20..6a77fbe6dc7 100644 --- a/abjad/get.py +++ b/abjad/get.py @@ -797,6 +797,48 @@ def duration( AfterGraceContainer("fs'16") Duration(1, 16) Note("fs'16") Duration(1, 16) + .. container:: example + + REGRESSION. Duration of independent after-grace containers defined equal to 0: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... duration = abjad.get.duration(component) + ... print(f"{repr(component):30} {repr(duration)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") Duration(1, 1) + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') Duration(1, 1) + Note("c'4") Duration(1, 4) + Note("d'4") Duration(1, 4) + Note("e'4") Duration(1, 4) + IndependentAfterGraceContainer("gf'16") Duration(0, 1) + Note("gf'16") Duration(1, 16) + Note("f'4") Duration(1, 4) + .. container:: example REGRESSSION. Works with tremolo containers: @@ -1003,6 +1045,50 @@ def effective( AfterGraceContainer("fs'16") Clef(name='alto', hide=False) Note("fs'16") Clef(name='alto', hide=False) + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.attach(abjad.Clef("alto"), container[0]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \clef "alto" + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... clef = abjad.get.effective(component, abjad.Clef) + ... print(f"{repr(component):30} {repr(clef)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") None + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') None + Note("c'4") None + Note("d'4") None + Note("e'4") None + IndependentAfterGraceContainer("gf'16") Clef(name='alto', hide=False) + Note("gf'16") Clef(name='alto', hide=False) + Note("f'4") Clef(name='alto', hide=False) + .. container:: example REGRESSSION. Works with tremolo containers: @@ -1750,8 +1836,55 @@ def grace(argument) -> bool: AfterGraceContainer("fs'16") True Note("fs'16") True + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... result = abjad.get.grace(component) + ... print(f"{repr(component):30} {repr(result)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") False + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') False + Note("c'4") False + Note("d'4") False + Note("e'4") False + IndependentAfterGraceContainer("gf'16") True + Note("gf'16") True + Note("f'4") False + """ - return _getlib._get_grace_container(argument) + if _getlib._get_grace_container(argument) is True: + return True + for component in argument._get_parentage(): + if isinstance(component, _score.IndependentAfterGraceContainer): + return True + return False def has_effective_indicator( @@ -1851,6 +1984,51 @@ def has_effective_indicator( AfterGraceContainer("fs'16") True Note("fs'16") True + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.attach(abjad.Clef("alto"), container[0]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \clef "alto" + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... function = abjad.get.has_effective_indicator + ... result = function(component, abjad.Clef) + ... print(f"{repr(component):30} {repr(result)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") False + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') False + Note("c'4") False + Note("d'4") False + Note("e'4") False + IndependentAfterGraceContainer("gf'16") True + Note("gf'16") True + Note("f'4") True + .. container:: example REGRESSSION. Works with tremolo containers: @@ -2001,6 +2179,50 @@ def has_indicator( AfterGraceContainer("fs'16") False Note("fs'16") False + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.attach(abjad.Clef("alto"), container[0]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \clef "alto" + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... result = abjad.get.has_indicator(component, abjad.Clef) + ... print(f"{repr(component):30} {repr(result)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") False + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') False + Note("c'4") False + Note("d'4") False + Note("e'4") False + IndependentAfterGraceContainer("gf'16") False + Note("gf'16") True + Note("f'4") False + .. container:: example REGRESSSION. Works with tremolo containers: @@ -2622,6 +2844,64 @@ def leaf(argument, n: int = 0) -> typing.Optional["_score.Leaf"]: next leaf: None --- + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for current_leaf in abjad.select.leaves(staff): + ... previous_leaf = abjad.get.leaf(current_leaf, -1) + ... next_leaf = abjad.get.leaf(current_leaf, 1) + ... print(f"previous leaf: {repr(previous_leaf)}") + ... print(f"current leaf: {repr(current_leaf)}") + ... print(f"next leaf: {repr(next_leaf)}") + ... print("---") + previous leaf: None + current leaf: Note("c'4") + next leaf: Note("d'4") + --- + previous leaf: Note("c'4") + current leaf: Note("d'4") + next leaf: Note("e'4") + --- + previous leaf: Note("d'4") + current leaf: Note("e'4") + next leaf: Note("gf'16") + --- + previous leaf: Note("e'4") + current leaf: Note("gf'16") + next leaf: Note("f'4") + --- + previous leaf: Note("gf'16") + current leaf: Note("f'4") + next leaf: None + --- + .. container:: example REGRESSSION. Works with tremolo containers: @@ -3170,6 +3450,48 @@ def measure_number(argument) -> int: AfterGraceContainer("fs'16") 1 Note("fs'16") 1 + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... measure_number = abjad.get.measure_number(component) + ... print(f"{repr(component):30} {measure_number}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") 1 + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') 1 + Note("c'4") 1 + Note("d'4") 1 + Note("e'4") 1 + IndependentAfterGraceContainer("gf'16") 1 + Note("gf'16") 1 + Note("f'4") 1 + .. container:: example REGRESSION. Measure number of score-initial grace notes is set equal to 0: @@ -3406,6 +3728,72 @@ def parentage(argument) -> "_parentage.Parentage": Voice("c'4 d'4 { { 16 gs'16 a'16 as'16 } { e'4 } } f'4", name='MusicVoice') Staff("{ c'4 d'4 { { 16 gs'16 a'16 as'16 } { e'4 } } f'4 }") + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... parentage = abjad.get.parentage(component) + ... print(f"{repr(component)}:") + ... for component_ in parentage[:]: + ... print(f" {repr(component_)}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }"): + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice'): + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Note("c'4"): + Note("c'4") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Note("d'4"): + Note("d'4") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Note("e'4"): + Note("e'4") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + IndependentAfterGraceContainer("gf'16"): + IndependentAfterGraceContainer("gf'16") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Note("gf'16"): + Note("gf'16") + IndependentAfterGraceContainer("gf'16") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + Note("f'4"): + Note("f'4") + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice') + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }") + .. container:: example REGRESSSION. Works with tremolo containers: @@ -3830,6 +4218,57 @@ def timespan(argument, in_seconds: bool = False) -> _timespan.Timespan: Note("fs'16"): Timespan(Offset((1, 1), displacement=Duration(-1, 16)), Offset((1, 1))) + .. container:: example + + REGRESSION. Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + gf'16 + } + f'4 + } + } + + >>> for component in abjad.select.components(staff): + ... timespan = abjad.get.timespan(component) + ... print(f"{component!r}:") + ... print(f" {timespan!r}") + Staff("{ c'4 d'4 e'4 { gf'16 } f'4 }"): + Timespan(Offset((0, 1)), Offset((1, 1))) + Voice("c'4 d'4 e'4 { gf'16 } f'4", name='MusicVoice'): + Timespan(Offset((0, 1)), Offset((1, 1))) + Note("c'4"): + Timespan(Offset((0, 1)), Offset((1, 4))) + Note("d'4"): + Timespan(Offset((1, 4)), Offset((1, 2))) + Note("e'4"): + Timespan(Offset((1, 2)), Offset((3, 4))) + IndependentAfterGraceContainer("gf'16"): + Timespan(Offset((3, 4), displacement=Duration(-1, 16)), Offset((3, 4))) + Note("gf'16"): + Timespan(Offset((3, 4), displacement=Duration(-1, 16)), Offset((3, 4))) + Note("f'4"): + Timespan(Offset((3, 4)), Offset((1, 1))) + .. container:: example REGRESSSION. Works with tremolo containers: diff --git a/abjad/meter.py b/abjad/meter.py index 4f66924c3cc..31f101fe5d6 100644 --- a/abjad/meter.py +++ b/abjad/meter.py @@ -2390,8 +2390,15 @@ def recurse( 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 + nongrace_components = [ + _ + for _ in components + if not isinstance(_, _score.IndependentAfterGraceContainer) + ] + # first_start_offset = components[0]._get_timespan().start_offset + # last_start_offset = components[-1]._get_timespan().start_offset + first_start_offset = nongrace_components[0]._get_timespan().start_offset + last_start_offset = nongrace_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. diff --git a/abjad/mutate.py b/abjad/mutate.py index d7965ee12f4..2622851ae87 100644 --- a/abjad/mutate.py +++ b/abjad/mutate.py @@ -2211,6 +2211,68 @@ def split( d'2 } + .. container:: example + + REGRESSION. Leaf independent after-grace leaves unchanged: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("af'4 gf'4") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> lilypond_file = abjad.LilyPondFile([staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + af'4 + gf'4 + } + f'4 + } + } + + >>> result = abjad.mutate.split(music_voice[:], [(1, 8)], cyclic=True) + >>> abjad.show(staff) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + { + \context Voice = "MusicVoice" + { + c'8 + ~ + c'8 + d'8 + ~ + d'8 + e'8 + ~ + \afterGrace + e'8 + { + af'4 + gf'4 + } + f'8 + ~ + f'8 + } + } + """ components = argument if isinstance(components, _score.Component): diff --git a/abjad/score.py b/abjad/score.py index 0656699bdb8..0b67e85fd76 100644 --- a/abjad/score.py +++ b/abjad/score.py @@ -591,6 +591,12 @@ def _format_opening_site(self, contributions): # IMPORTANT: LilyPond \afterGrace must appear immediately before leaf! # TODO: figure out \pitchedTrill, \afterGrace ordering if self._after_grace_container is not None: + assert not self._is_followed_by_after_grace_container() + result.append(r"\afterGrace") + if self._is_followed_by_after_grace_container(): + assert self._after_grace_container is None, repr( + self._after_grace_container + ) result.append(r"\afterGrace") return result @@ -628,6 +634,18 @@ def _get_subtree(self): result.extend(self._after_grace_container._get_subtree()) return result + def _is_followed_by_after_grace_container(self): + if self._parent is not None: + index = self._parent.index(self) + try: + component = self._parent[index + 1] + except IndexError: + component = None + if isinstance(component, IndependentAfterGraceContainer): + return True + else: + return False + def _process_contribution_packet(self, contribution_packet): result = "" for contributor, contributions in contribution_packet: @@ -2577,7 +2595,7 @@ def __init__( if isinstance(written_pitches, str): written_pitches = [_ for _ in written_pitches.split() if _] elif isinstance(written_pitches, type(self)): - written_pitches = written_pitches.written_pitches + written_pitches = list(written_pitches.written_pitches) elif len(arguments) == 0: written_pitches = [_pitch.NamedPitch(_) for _ in [0, 4, 7]] written_duration = _duration.Duration(1, 4) @@ -3218,6 +3236,82 @@ def tag(self, argument) -> None: self._tag = argument +class IndependentAfterGraceContainer(Container): + r""" + Independent after grace container. + + .. container:: example + + After grace notes: + + >>> voice = abjad.Voice("c'4 d'4 e'4 f'4") + >>> string = '#(define afterGraceFraction (cons 15 16))' + >>> literal = abjad.LilyPondLiteral(string, site="before") + >>> abjad.attach(literal, voice[0]) + >>> notes = [abjad.Note("c'16"), abjad.Note("d'16")] + >>> after_grace_container = abjad.IndependentAfterGraceContainer(notes) + >>> voice.insert(2, after_grace_container) + >>> abjad.show(voice) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(voice) + >>> print(string) + \new Voice + { + #(define afterGraceFraction (cons 15 16)) + c'4 + \afterGrace + d'4 + { + c'16 + d'16 + } + e'4 + f'4 + } + + LilyPond positions after grace notes at a point 3/4 of the way + after the note they follow. The resulting spacing is usually too + loose. + + Customize ``afterGraceFraction`` as shown above. + + After grace notes are played in the last moments of the duration of the + note they follow. + + Use after grace notes when you need to end a piece of music with grace + notes. + + Fill grace containers with notes, rests or chords. + + """ + + ### CLASS VARIABLES ### + + __documentation_section__ = "Containers" + + ### SPECIAL METHODS ### + + def __getnewargs__(self): + """ + Gets new after grace container arguments. + + Returns tuple of single empty list. + """ + return ([],) + + ### PRIVATE METHODS ### + + def _format_open_brackets_site(self, contributions): + result = [] + result.extend(["{"]) + return result + + def _get_preprolated_duration(self): + return _duration.Duration(0) + + class MultimeasureRest(Leaf): r""" Multimeasure rest. diff --git a/abjad/select.py b/abjad/select.py index 8d84258dc25..261feefe9cf 100644 --- a/abjad/select.py +++ b/abjad/select.py @@ -6,6 +6,7 @@ from . import cyclictuple as _cyclictuple from . import duration as _duration from . import enums as _enums +from . import get as _get from . import iterate as _iterate from . import math as _math from . import parentage as _parentage @@ -571,6 +572,56 @@ def components( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.components(staff, abjad.Leaf, grace=None) + >>> for item in result: + ... item + ... + Note("c'4") + Note("d'4") + Note("e'4") + Note("gf'16") + Note("f'4") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + \abjad-color-music #'blue + gf'16 + } + \abjad-color-music #'red + f'4 + } + } + .. container:: example Excludes grace notes when ``grace=False``: @@ -624,6 +675,54 @@ def components( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.components(staff, abjad.Leaf, grace=False) + >>> for item in result: + ... item + ... + Note("c'4") + Note("d'4") + Note("e'4") + Note("f'4") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + gf'16 + } + \abjad-color-music #'blue + f'4 + } + } + .. container:: example Selects only grace notes when ``grace=True``: @@ -677,11 +776,64 @@ def components( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.components(staff, abjad.Leaf, grace=True) + >>> for item in result: + ... item + ... + Note("gf'16") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + f'4 + } + } + """ generator = _iterlib._public_iterate_components( - argument, prototype=prototype, exclude=exclude, grace=grace, reverse=reverse + argument, prototype=prototype, exclude=exclude, grace=None, reverse=reverse ) - return list(generator) + result = [] + for component in generator: + if ( + grace is None + or (grace is True and _get.grace(component)) + or (grace is False and not _get.grace(component)) + ): + result.append(component) + return result + + +_components_alias = components def exclude(argument, indices: typing.Sequence[int], period: int | None = None) -> list: @@ -3215,6 +3367,56 @@ def leaves( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.leaves(staff, grace=None) + >>> for item in result: + ... item + ... + Note("c'4") + Note("d'4") + Note("e'4") + Note("gf'16") + Note("f'4") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + \abjad-color-music #'blue + gf'16 + } + \abjad-color-music #'red + f'4 + } + } + .. container:: example Excludes grace notes when ``grace=False``: @@ -3268,6 +3470,54 @@ def leaves( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.leaves(staff, grace=False) + >>> for item in result: + ... item + ... + Note("c'4") + Note("d'4") + Note("e'4") + Note("f'4") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + gf'16 + } + \abjad-color-music #'blue + f'4 + } + } + .. container:: example Selects only grace notes when ``grace=True``: @@ -3321,6 +3571,48 @@ def leaves( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.leaves(staff, grace=True) + >>> for item in result: + ... item + ... + Note("gf'16") + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + f'4 + } + } + ''' assert trim in (True, False, _enums.LEFT, None) if pitched: @@ -3331,10 +3623,9 @@ def leaves( if not isinstance(prototype, tuple): prototype = (prototype,) result = [] - generator = _iterlib._public_iterate_components( - argument, prototype, exclude=exclude, grace=grace + components = _components_alias( + argument, prototype=prototype, exclude=exclude, grace=grace, reverse=reverse ) - components = list(generator) if components: if trim in (True, _enums.LEFT): components = _trim_subresult(components, trim) @@ -3680,7 +3971,118 @@ def logical_ties( .. container:: example - Selects both main notes and graces when ``grace=None``: + Selects both main notes and graces when ``grace=None``: + + >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") + >>> container = abjad.BeforeGraceContainer("cf''16 bf'16") + >>> abjad.attach(container, staff[1]) + >>> container = abjad.AfterGraceContainer("af'16 gf'16") + >>> abjad.attach(container, staff[1]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.logical_ties(staff, grace=None) + >>> for item in result: + ... item + ... + LogicalTie(items=[Note("c'8")]) + LogicalTie(items=[Note("cf''16")]) + LogicalTie(items=[Note("bf'16")]) + LogicalTie(items=[Note("d'8")]) + LogicalTie(items=[Note("af'16")]) + LogicalTie(items=[Note("gf'16")]) + LogicalTie(items=[Note("e'8")]) + LogicalTie(items=[Note("f'8")]) + + >>> abjad.label.by_selector(result) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \abjad-color-music #'red + c'8 + \grace { + \abjad-color-music #'blue + cf''16 + \abjad-color-music #'red + bf'16 + } + \abjad-color-music #'blue + \afterGrace + d'8 + { + \abjad-color-music #'red + af'16 + \abjad-color-music #'blue + gf'16 + } + \abjad-color-music #'red + e'8 + \abjad-color-music #'blue + f'8 + } + + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.logical_ties(staff, grace=None) + >>> for item in result: + ... item + ... + LogicalTie(items=[Note("c'4")]) + LogicalTie(items=[Note("d'4")]) + LogicalTie(items=[Note("e'4")]) + LogicalTie(items=[Note("gf'16")]) + LogicalTie(items=[Note("f'4")]) + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + \abjad-color-music #'blue + gf'16 + } + \abjad-color-music #'red + f'4 + } + } + + .. container:: example + + Excludes grace notes when ``grace=False``: >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") >>> container = abjad.BeforeGraceContainer("cf''16 bf'16") @@ -3689,16 +4091,12 @@ def logical_ties( >>> abjad.attach(container, staff[1]) >>> abjad.setting(staff).autoBeaming = False - >>> result = abjad.select.logical_ties(staff, grace=None) + >>> result = abjad.select.logical_ties(staff, grace=False) >>> for item in result: ... item ... LogicalTie(items=[Note("c'8")]) - LogicalTie(items=[Note("cf''16")]) - LogicalTie(items=[Note("bf'16")]) LogicalTie(items=[Note("d'8")]) - LogicalTie(items=[Note("af'16")]) - LogicalTie(items=[Note("gf'16")]) LogicalTie(items=[Note("e'8")]) LogicalTie(items=[Note("f'8")]) @@ -3719,18 +4117,14 @@ def logical_ties( \abjad-color-music #'red c'8 \grace { - \abjad-color-music #'blue cf''16 - \abjad-color-music #'red bf'16 } \abjad-color-music #'blue \afterGrace d'8 { - \abjad-color-music #'red af'16 - \abjad-color-music #'blue gf'16 } \abjad-color-music #'red @@ -3739,27 +4133,24 @@ def logical_ties( f'8 } - .. container:: example - - Excludes grace notes when ``grace=False``: + Works with independent after-grace containers: - >>> staff = abjad.Staff("c'8 d'8 e'8 f'8") - >>> container = abjad.BeforeGraceContainer("cf''16 bf'16") - >>> abjad.attach(container, staff[1]) - >>> container = abjad.AfterGraceContainer("af'16 gf'16") - >>> abjad.attach(container, staff[1]) + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) >>> abjad.setting(staff).autoBeaming = False >>> result = abjad.select.logical_ties(staff, grace=False) >>> for item in result: ... item ... - LogicalTie(items=[Note("c'8")]) - LogicalTie(items=[Note("d'8")]) - LogicalTie(items=[Note("e'8")]) - LogicalTie(items=[Note("f'8")]) + LogicalTie(items=[Note("c'4")]) + LogicalTie(items=[Note("d'4")]) + LogicalTie(items=[Note("e'4")]) + LogicalTie(items=[Note("f'4")]) - >>> abjad.label.by_selector(result) + >>> abjad.label.by_selector(result, True) >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) >>> abjad.show(lilypond_file) # doctest: +SKIP @@ -3773,23 +4164,21 @@ def logical_ties( autoBeaming = ##f } { - \abjad-color-music #'red - c'8 - \grace { - cf''16 - bf'16 - } - \abjad-color-music #'blue - \afterGrace - d'8 + \context Voice = "MusicVoice" { - af'16 - gf'16 + \abjad-color-music #'red + c'4 + \abjad-color-music #'blue + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + gf'16 + } + \abjad-color-music #'blue + f'4 } - \abjad-color-music #'red - e'8 - \abjad-color-music #'blue - f'8 } .. container:: example @@ -3846,16 +4235,66 @@ def logical_ties( f'8 } + Works with independent after-grace containers: + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> result = abjad.select.logical_ties(staff, grace=True) + >>> for item in result: + ... item + ... + LogicalTie(items=[Note("gf'16")]) + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + f'4 + } + } + ''' generator = _iterlib._iterate_logical_ties( argument, exclude=exclude, - grace=grace, + grace=None, nontrivial=nontrivial, pitched=pitched, reverse=reverse, ) - return list(generator) + result = [] + for logical_tie in generator: + if ( + grace is None + or (grace is True and _get.grace(logical_tie.head)) + or (grace is False and not _get.grace(logical_tie.head)) + ): + result.append(logical_tie) + return result def nontrivial(argument) -> list: @@ -6701,6 +7140,94 @@ def with_next_leaf(argument, *, grace: bool | None = None) -> list[_score.Leaf]: } } + Works with independent after-grace containers (grace-to-main): + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> leaves = abjad.select.leaves(staff) + >>> result = [abjad.select.with_next_leaf(_) for _ in [leaves[2:3]]] + >>> for item in result: + ... item + ... + [Note("e'4"), Note("gf'16")] + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + f'4 + } + } + + Works with independent after-grace containers (grace-to-main): + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> leaves = abjad.select.leaves(staff) + >>> result = [abjad.select.with_next_leaf(_) for _ in [leaves[3:4]]] + >>> for item in result: + ... item + ... + [Note("gf'16"), Note("f'4")] + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + \abjad-color-music #'red + f'4 + } + } + """ items = leaves(argument) previous_leaf = items[-1] @@ -6911,6 +7438,94 @@ def with_previous_leaf(argument) -> list[_score.Leaf]: } } + Works with independent after-grace containers (grace-to-main): + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> leaves = abjad.select.leaves(staff) + >>> result = [abjad.select.with_previous_leaf(_) for _ in [leaves[3:4]]] + >>> for item in result: + ... item + ... + [Note("e'4"), Note("gf'16")] + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \abjad-color-music #'red + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + f'4 + } + } + + Works with independent after-grace containers (main-to-grace): + + >>> music_voice = abjad.Voice("c'4 d' e' f'", name="MusicVoice") + >>> container = abjad.IndependentAfterGraceContainer("gf'16") + >>> music_voice.insert(3, container) + >>> staff = abjad.Staff([music_voice]) + >>> abjad.setting(staff).autoBeaming = False + + >>> leaves = abjad.select.leaves(staff) + >>> result = [abjad.select.with_previous_leaf(_) for _ in [leaves[-1:]]] + >>> for item in result: + ... item + ... + [Note("gf'16"), Note("f'4")] + + >>> abjad.label.by_selector(result, True) + >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) + >>> abjad.show(lilypond_file) # doctest: +SKIP + + .. docs:: + + >>> string = abjad.lilypond(staff) + >>> print(string) + \new Staff + \with + { + autoBeaming = ##f + } + { + \context Voice = "MusicVoice" + { + c'4 + d'4 + \afterGrace + e'4 + { + \abjad-color-music #'red + gf'16 + } + \abjad-color-music #'red + f'4 + } + } + """ items = leaves(argument) previous_leaf = _iterlib._get_leaf(items[0], n=-1) diff --git a/abjad/spanners.py b/abjad/spanners.py index 65f77ff0bbf..d53e325e1e3 100644 --- a/abjad/spanners.py +++ b/abjad/spanners.py @@ -324,7 +324,6 @@ def glissando( parenthesize_repeats: bool = False, right_broken: bool = False, right_broken_show_next: bool = False, - style: str | None = None, tag: _tag.Tag | None = None, zero_padding: bool = False, ): @@ -1112,7 +1111,7 @@ def _previous_leaf_changes_current_pitch(leaf): ] literal = _indicators.LilyPondLiteral(strings, site="before") _bind.attach(literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(-1)"))) - if hide_middle_note_heads: + if hide_middle_note_heads and 3 <= len(leaves): if leaf is not leaves[0]: should_attach_glissando = False if not left_broken and leaf is leaves[1]: