Source code for abjad.spanners

"""
Classes and functions for modeling spanners: beams, hairpins, slurs, etc.
"""
import typing

from . import _iterlib
from . import bind as _bind
from . import duration as _duration
from . import dynamic as _dynamic
from . import enums as _enums
from . import indicators as _indicators
from . import iterate as _iterate
from . import parentage as _parentage
from . import score as _score
from . import select as _select
from . import sequence as _sequence
from . import tag as _tag
from . import tweaks as _tweaks


def _apply_tweaks(argument, tweaks, i=None, total=None):
    if not tweaks:
        return argument
    if i is not None:
        assert isinstance(i, int), repr(i)
    if total is not None:
        assert isinstance(total, int), repr(total)
    tweak_objects = []
    for item in tweaks:
        if isinstance(item, tuple):
            assert len(item) == 2
            item, index = item
            if 0 <= index and index != i:
                continue
            if index < 0 and index != -(total - i):
                continue
        assert isinstance(item, _tweaks.Tweak), repr(item)
        tweak_objects.append(item)
    bundle = _tweaks.bundle(argument, *tweak_objects)
    return bundle


[docs]def beam( argument: _score.Component | typing.Sequence[_score.Component], *, beam_lone_notes: bool = False, beam_rests: bool | None = True, direction: _enums.Vertical | None = None, durations: typing.Sequence[_duration.Duration] | None = None, selector: typing.Callable = lambda _: _select.leaves(_), span_beam_count: int | None = None, start_beam: _indicators.StartBeam | None = None, stemlet_length: int | float | None = None, stop_beam: _indicators.StopBeam | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches beam indicators. .. container:: example >>> voice = abjad.Voice("c'8 d' e' f'") >>> abjad.beam(voice[:], direction=abjad.UP) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'8 ^ [ d'8 e'8 f'8 ] } """ assert callable(selector) argument = selector(argument) original_leaves = list(_iterate.leaves(argument)) silent_prototype = (_score.MultimeasureRest, _score.Rest, _score.Skip) def _is_beamable(argument, beam_rests=False): if isinstance(argument, _score.Chord | _score.Note): if 0 < argument.written_duration.flag_count: return True if beam_rests and isinstance(argument, silent_prototype): return True return False leaves = [] for leaf in original_leaves: if not _is_beamable(leaf, beam_rests=beam_rests): continue leaves.append(leaf) runs = [] run = [] run.extend(leaves[:1]) for leaf in leaves[1:]: this_index = original_leaves.index(run[-1]) that_index = original_leaves.index(leaf) if this_index + 1 == that_index: run.append(leaf) else: runs.append(run) run = [leaf] if run: runs.append(run) runs_ = runs if not beam_lone_notes: result = _select.nontrivial(runs) runs_ = result for run in runs_: if all(isinstance(_, silent_prototype) for _ in run): continue start_leaf = run[0] stop_leaf = run[-1] start_beam_ = start_beam or _indicators.StartBeam() stop_beam_ = stop_beam or _indicators.StopBeam() _bind.detach(_indicators.StartBeam, start_leaf) _bind.attach(start_beam_, start_leaf, direction=direction, tag=tag) _bind.detach(_indicators.StopBeam, stop_leaf) _bind.attach(stop_beam_, stop_leaf, tag=tag) if stemlet_length is None: continue staff = _parentage.Parentage(start_leaf).get(_score.Staff) lilypond_type = getattr(staff, "lilypond_type", "Staff") string = rf"\override {lilypond_type}.Stem.stemlet-length = {stemlet_length}" literal = _indicators.LilyPondLiteral(string, site="before") for indicator in start_leaf._get_indicators(): if indicator == literal: break else: _bind.attach(literal, start_leaf, tag=tag) staff = _parentage.Parentage(stop_leaf).get(_score.Staff) lilypond_type = getattr(staff, "lilypond_type", "Staff") string = rf"\revert {lilypond_type}.Stem.stemlet-length" literal = _indicators.LilyPondLiteral(string, site="after") for indicator in stop_leaf._get_indicators(): if indicator == literal: break else: _bind.attach(literal, stop_leaf, tag=tag) if not durations: return if len(original_leaves) == 1: return def _leaf_neighbors(leaf, original_leaves): assert leaf is not original_leaves[0] assert leaf is not original_leaves[-1] this_index = original_leaves.index(leaf) previous_leaf = original_leaves[this_index - 1] previous = 0 if _is_beamable(previous_leaf, beam_rests=beam_rests): previous = previous_leaf.written_duration.flag_count next_leaf = original_leaves[this_index + 1] next_ = 0 if _is_beamable(next_leaf, beam_rests=beam_rests): next_ = next_leaf.written_duration.flag_count return previous, next_ span_beam_count = span_beam_count or 1 durations = [_duration.Duration(_) for _ in durations] leaf_durations = [_._get_duration() for _ in original_leaves] parts = _sequence.partition_by_weights(leaf_durations, durations, overhang=True) part_counts = [len(_) for _ in parts] parts = _sequence.partition_by_counts(original_leaves, part_counts) total_parts = len(parts) for i, part in enumerate(parts): is_first_part = False if i == 0: is_first_part = True is_last_part = False if i == total_parts - 1: is_last_part = True first_leaf = part[0] flag_count = first_leaf.written_duration.flag_count if len(part) == 1: if not _is_beamable(first_leaf, beam_rests=False): continue left = flag_count right = flag_count beam_count = _indicators.BeamCount(left, right) _bind.attach(beam_count, first_leaf, tag=tag) continue if _is_beamable(first_leaf, beam_rests=False): if is_first_part: left = 0 else: left = span_beam_count beam_count = _indicators.BeamCount(left, flag_count) _bind.attach(beam_count, first_leaf, tag=tag) last_leaf = part[-1] if _is_beamable(last_leaf, beam_rests=False): flag_count = last_leaf.written_duration.flag_count if is_last_part: left = flag_count right = 0 else: previous, next_ = _leaf_neighbors(last_leaf, original_leaves) if previous == next_ == 0: left = right = flag_count elif previous == 0: left = 0 right = flag_count elif next_ == 0: left = flag_count right = 0 elif previous == flag_count: left = flag_count right = min(span_beam_count, next_) elif flag_count == next_: left = min(previous, flag_count) right = flag_count else: left = flag_count right = min(previous, flag_count) beam_count = _indicators.BeamCount(left, right) _bind.attach(beam_count, last_leaf, tag=tag) # TODO: eventually remove middle leaf beam counts? for middle_leaf in part[1:-1]: if not _is_beamable(middle_leaf, beam_rests=beam_rests): continue if isinstance(middle_leaf, silent_prototype): continue flag_count = middle_leaf.written_duration.flag_count previous, next_ = _leaf_neighbors(middle_leaf, original_leaves) if previous == next_ == 0: left = right = flag_count elif previous == 0: left = 0 right = flag_count elif next_ == 0: left = flag_count right = 0 elif previous == flag_count: left = flag_count right = min(flag_count, next_) elif flag_count == next_: left = min(previous, flag_count) right = flag_count else: left = min(previous, flag_count) right = flag_count beam_count = _indicators.BeamCount(left, right) _bind.attach(beam_count, middle_leaf, tag=tag)
[docs]def glissando( argument, *tweaks, allow_repeats: bool = False, allow_ties: bool = False, hide_middle_note_heads: bool = False, hide_middle_stems: bool = False, hide_stem_selector: typing.Callable | None = None, left_broken: bool = False, 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, ): r""" Attaches glissando indicators. .. container:: example >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando(voice[:]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { c'8 \glissando d'8 \glissando e'8 \glissando f'8 } } .. container:: example Glissando avoids bend-after indicators: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> bend_after = abjad.BendAfter() >>> abjad.attach(bend_after, voice[1]) >>> abjad.glissando(voice[:]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { c'8 \glissando d'8 - \bendAfter #'-4 e'8 \glissando f'8 } } .. container:: example Does not allow repeated pitches: >>> voice = abjad.Voice("a8 a8 b8 ~ b8 c'8 c'8 d'8 ~ d'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando(voice[:], allow_repeats=True) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { a8 \glissando a8 \glissando b8 ~ b8 \glissando c'8 \glissando c'8 \glissando d'8 ~ d'8 } } .. container:: example Allows repeated pitches (but not ties): >>> voice = abjad.Voice("a8 a8 b8 ~ b8 c'8 c'8 d'8 ~ d'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando(voice[:], allow_repeats=True) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { a8 \glissando a8 \glissando b8 ~ b8 \glissando c'8 \glissando c'8 \glissando d'8 ~ d'8 } } .. container:: example Allows both repeated pitches and ties: >>> voice = abjad.Voice("a8 a8 b8 ~ b8 c'8 c'8 d'8 ~ d'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando( ... voice[:], ... allow_repeats=True, ... allow_ties=True, ... ) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { a8 \glissando a8 \glissando b8 \glissando ~ b8 \glissando c'8 \glissando c'8 \glissando d'8 \glissando ~ d'8 } } Ties are excluded when repeated pitches are not allowed because all ties comprise repeated pitches. .. container:: example Spans and parenthesizes repeated pitches: >>> voice = abjad.Voice("a8 a8 b8 ~ b8 c'8 c'8 d'8 ~ d'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando( ... voice[:], ... allow_repeats=True, ... parenthesize_repeats=True, ... ) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { a8 \glissando \parenthesize a8 \glissando b8 ~ \parenthesize b8 \glissando c'8 \glissando \parenthesize c'8 \glissando d'8 ~ \parenthesize d'8 } } .. container:: example Parenthesizes (but does not span) repeated pitches: >>> voice = abjad.Voice("a8 a8 b8 ~ b8 c'8 c'8 d'8 ~ d'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando( ... voice[:], ... parenthesize_repeats=True, ... ) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { a8 \parenthesize a8 \glissando b8 ~ \parenthesize b8 \glissando c'8 \parenthesize c'8 \glissando d'8 ~ \parenthesize d'8 } } .. container:: example With ``hide_middle_note_heads=True``: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> staff = abjad.Staff([voice], name="Staff") >>> abjad.glissando(voice[:], hide_middle_note_heads=True) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \context Staff = "Staff" { \context Voice = "Voice" { c'8 \glissando \hide NoteHead \override Accidental.stencil = ##f \override NoteColumn.glissando-skip = ##t \override NoteHead.no-ledgers = ##t d'8 e'8 \revert Accidental.stencil \revert NoteColumn.glissando-skip \revert NoteHead.no-ledgers \undo \hide NoteHead f'8 } } .. container:: example With ``hide_middle_note_heads=True`` and ``hide_middle_stems=True``: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando( ... voice[:], ... hide_middle_note_heads=True, ... hide_middle_stems=True, ... ) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'8 \glissando \hide NoteHead \override Accidental.stencil = ##f \override NoteColumn.glissando-skip = ##t \override NoteHead.no-ledgers = ##t \override Dots.transparent = ##t \override Stem.transparent = ##t d'8 e'8 \revert Accidental.stencil \revert NoteColumn.glissando-skip \revert NoteHead.no-ledgers \undo \hide NoteHead \revert Dots.transparent \revert Stem.transparent f'8 } .. note:: Respects ``hide_middle_stems`` only when ``hide_middle_note_heads=True``. .. container:: example With ``right_broken=True``: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando(voice[:], right_broken=True) >>> abjad.show(voice) # doctest: +SKIP LilyPond output looks like this: >>> string = abjad.lilypond(voice, tags=True) >>> print(string) \context Voice = "Voice" { c'8 %! abjad.glissando(7) \glissando d'8 %! abjad.glissando(7) \glissando e'8 %! abjad.glissando(7) \glissando f'8 %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(7) %@% \glissando } .. container:: example With ``right_broken=True`` and ``hide_middle_note_heads=True``: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando( ... voice[:], ... right_broken=True, ... hide_middle_note_heads=True, ... ) >>> abjad.show(voice) # doctest: +SKIP LilyPond output looks like this: >>> string = abjad.lilypond(voice, tags=True) >>> print(string) \context Voice = "Voice" { c'8 %! abjad.glissando(7) \glissando %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \hide NoteHead %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override Accidental.stencil = ##f %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override NoteColumn.glissando-skip = ##t %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override NoteHead.no-ledgers = ##t d'8 e'8 %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert Accidental.stencil %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert NoteColumn.glissando-skip %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert NoteHead.no-ledgers %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \undo \hide NoteHead f'8 } .. container:: example With ``right_broken=True``, ``hide_middle_note_heads=True`` and ``right_broken_show_next=True``: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando( ... voice[:], ... hide_middle_note_heads=True, ... right_broken=True, ... right_broken_show_next=True, ... ) >>> abjad.show(voice) # doctest: +SKIP LilyPond output looks like this: >>> string = abjad.lilypond(voice, tags=True) >>> print(string) \context Voice = "Voice" { c'8 %! abjad.glissando(7) \glissando %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \hide NoteHead %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override Accidental.stencil = ##f %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override NoteColumn.glissando-skip = ##t %! RIGHT_BROKEN %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(0) \override NoteHead.no-ledgers = ##t d'8 e'8 %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert Accidental.stencil %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert NoteColumn.glissando-skip %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \revert NoteHead.no-ledgers %! HIDE_TO_JOIN_BROKEN_SPANNERS %! RIGHT_BROKEN %! abjad.glissando(4) \undo \hide NoteHead f'8 %! RIGHT_BROKEN_SHOW_NEXT %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(5) %@% \revert Accidental.stencil %! RIGHT_BROKEN_SHOW_NEXT %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(5) %@% \revert NoteColumn.glissando-skip %! RIGHT_BROKEN_SHOW_NEXT %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(5) %@% \revert NoteHead.no-ledgers %! RIGHT_BROKEN_SHOW_NEXT %! SHOW_TO_JOIN_BROKEN_SPANNERS %! abjad.glissando(5) %@% \undo \hide NoteHead } .. container:: example With ``left_broken=True`` (and ``hide_middle_note_heads=True``): >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando( ... voice[:], ... left_broken=True, ... hide_middle_note_heads=True, ... ) >>> abjad.show(voice) # doctest: +SKIP LilyPond output looks like this: >>> string = abjad.lilypond(voice, tags=True) >>> print(string) \context Voice = "Voice" { %! HIDE_TO_JOIN_BROKEN_SPANNERS %! LEFT_BROKEN %! abjad.glissando(2) \hide NoteHead %! HIDE_TO_JOIN_BROKEN_SPANNERS %! LEFT_BROKEN %! abjad.glissando(2) \override Accidental.stencil = ##f %! HIDE_TO_JOIN_BROKEN_SPANNERS %! LEFT_BROKEN %! abjad.glissando(2) \override NoteHead.no-ledgers = ##t c'8 %! abjad.glissando(7) \glissando %! HIDE_TO_JOIN_BROKEN_SPANNERS %! LEFT_BROKEN %! abjad.glissando(3) \override NoteColumn.glissando-skip = ##t d'8 e'8 %! abjad.glissando(6) \revert Accidental.stencil %! abjad.glissando(6) \revert NoteColumn.glissando-skip %! abjad.glissando(6) \revert NoteHead.no-ledgers %! abjad.glissando(6) \undo \hide NoteHead f'8 } .. note:: Respects left-broken only with ``hide_middle_note_heads=True``. .. container:: example Tweaks apply to every glissando: >>> voice = abjad.Voice("c'8 d'8 e'8 f'8", name="Voice") >>> abjad.glissando( ... voice[:], ... abjad.Tweak(r"- \tweak style #'trill"), ... ) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'8 - \tweak style #'trill \glissando d'8 - \tweak style #'trill \glissando e'8 - \tweak style #'trill \glissando f'8 } .. container:: example With ``zero_padding=True`` on fixed pitch: >>> voice = abjad.Voice("d'8 d'4. d'4. d'8", name="Voice") >>> abjad.glissando( ... voice[:], ... allow_repeats=True, ... zero_padding=True, ... ) >>> for note in voice[1:]: ... abjad.override(note).NoteHead.transparent = True ... abjad.override(note).NoteHead.X_extent = "#'(0 . 0)" ... >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { d'8 - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'4. - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'4. - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'8 } .. container:: example With ``zero_padding=True`` on changing pitches: >>> voice = abjad.Voice("c'8. d'8. e'8. f'8.", name="Voice") >>> abjad.glissando(voice[:], zero_padding=True) >>> for note in voice[1:-1]: ... abjad.override(note).NoteHead.transparent = True ... abjad.override(note).NoteHead.X_extent = "#'(0 . 0)" ... >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'8. - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'8. - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t e'8. - \abjad-zero-padding-glissando \glissando f'8. } .. container:: example With indexed tweaks: >>> voice = abjad.Voice("d'4 d' d' d'", name="Voice") >>> abjad.glissando( ... voice[:], ... (abjad.Tweak(r"- \tweak color #red"), 0), ... (abjad.Tweak(r"- \tweak color #red"), -1), ... allow_repeats=True, ... zero_padding=True, ... ) >>> for note in voice[1:-1]: ... abjad.override(note).NoteHead.transparent = True ... abjad.override(note).NoteHead.X_extent = "#'(0 . 0)" ... >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { d'4 - \abjad-zero-padding-glissando - \tweak color #red \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'4 - \abjad-zero-padding-glissando \glissando \once \override NoteHead.X-extent = #'(0 . 0) \once \override NoteHead.transparent = ##t d'4 - \abjad-zero-padding-glissando - \tweak color #red \glissando d'4 } """ tag = tag or _tag.Tag() if right_broken_show_next and not right_broken: raise Exception("set right_broken_show_next only when right_broken is true.") if hide_middle_stems and not hide_middle_note_heads: message = "set hide_middle_stems only when" message += " hide_middle_note_heads is true." raise Exception(message) def _is_last_in_tie_chain(leaf): logical_tie = _iterlib._get_logical_tie_leaves(leaf) return leaf is logical_tie[-1] def _next_leaf_changes_current_pitch(leaf): next_leaf = _iterlib._get_leaf(leaf, 1) if ( isinstance(leaf, _score.Note) and isinstance(next_leaf, _score.Note) and leaf.written_pitch == next_leaf.written_pitch ): return False elif ( isinstance(leaf, _score.Chord) and isinstance(next_leaf, _score.Chord) and leaf.written_pitches == next_leaf.written_pitches ): return False return True def _parenthesize_leaf(leaf): if isinstance(leaf, _score.Note): leaf.note_head.is_parenthesized = True elif isinstance(leaf, _score.Chord): for note_head in leaf.note_heads: note_head.is_parenthesized = True def _previous_leaf_changes_current_pitch(leaf): previous_leaf = _iterlib._get_leaf(leaf, -1) if ( isinstance(leaf, _score.Note) and isinstance(previous_leaf, _score.Note) and leaf.written_pitch == previous_leaf.written_pitch ): return False elif ( isinstance(leaf, _score.Chord) and isinstance(previous_leaf, _score.Chord) and leaf.written_pitches == previous_leaf.written_pitches ): return False return True should_hide_stem = [] if hide_stem_selector is not None: should_hide_stem = hide_stem_selector(argument) leaves = _select.leaves(argument) total = len(leaves) - 1 for i, leaf in enumerate(leaves): if leaf is not leaves[0]: if parenthesize_repeats: if not _previous_leaf_changes_current_pitch(leaf): _parenthesize_leaf(leaf) should_attach_glissando = False deactivate_glissando = False if leaf._has_indicator(_indicators.BendAfter): pass elif leaf is leaves[-1]: if right_broken is True: should_attach_glissando = True deactivate_glissando = True elif not isinstance(leaf, _score.Chord | _score.Note): pass elif allow_repeats and allow_ties: should_attach_glissando = True elif allow_repeats and not allow_ties: should_attach_glissando = _is_last_in_tie_chain(leaf) elif not allow_repeats and allow_ties: if _next_leaf_changes_current_pitch(leaf): should_attach_glissando = True elif not allow_repeats and not allow_ties: if _next_leaf_changes_current_pitch(leaf): if _is_last_in_tie_chain(leaf): should_attach_glissando = True if leaf in should_hide_stem: strings = [ r"\once \override Dots.transparent = ##t", r"\once \override Stem.transparent = ##t", ] literal = _indicators.LilyPondLiteral(strings, site="before") _bind.attach(literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(-1)"))) if hide_middle_note_heads: if leaf is not leaves[0]: should_attach_glissando = False if not left_broken and leaf is leaves[1]: strings = [ r"\hide NoteHead", r"\override Accidental.stencil = ##f", r"\override NoteColumn.glissando-skip = ##t", r"\override NoteHead.no-ledgers = ##t", ] if hide_middle_stems: strings.extend( [ r"\override Dots.transparent = ##t", r"\override Stem.transparent = ##t", ] ) literal = _indicators.LilyPondLiteral(strings, site="before") if right_broken is True: _bind.attach( literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(0)")) .append(_tag.Tag("SHOW_TO_JOIN_BROKEN_SPANNERS")) .append(_tag.Tag("RIGHT_BROKEN")), ) else: _bind.attach( literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(1)")), ) elif left_broken and leaf is leaves[0]: strings = [ r"\hide NoteHead", r"\override Accidental.stencil = ##f", r"\override NoteHead.no-ledgers = ##t", ] if hide_middle_stems: strings.extend( [ r"\override Dots.transparent = ##t", r"\override Stem.transparent = ##t", ] ) literal = _indicators.LilyPondLiteral(strings, site="before") _bind.attach( literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(2)")) .append(_tag.Tag("HIDE_TO_JOIN_BROKEN_SPANNERS")) .append(_tag.Tag("LEFT_BROKEN")), ) elif left_broken and leaf is leaves[1]: string = r"\override NoteColumn.glissando-skip = ##t" literal = _indicators.LilyPondLiteral(string, site="before") _bind.attach( literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(3)")) .append(_tag.Tag("HIDE_TO_JOIN_BROKEN_SPANNERS")) .append(_tag.Tag("LEFT_BROKEN")), ) if leaf is leaves[-1]: strings = [ r"\revert Accidental.stencil", r"\revert NoteColumn.glissando-skip", r"\revert NoteHead.no-ledgers", r"\undo \hide NoteHead", ] if hide_middle_stems: strings.extend( [r"\revert Dots.transparent", r"\revert Stem.transparent"] ) if right_broken: deactivate_glissando = True literal = _indicators.LilyPondLiteral(strings, site="before") _bind.attach( literal, leaf, deactivate=False, tag=tag.append(_tag.Tag("abjad.glissando(4)")) .append(_tag.Tag("HIDE_TO_JOIN_BROKEN_SPANNERS")) .append(_tag.Tag("RIGHT_BROKEN")), ) if right_broken_show_next: literal = _indicators.LilyPondLiteral(strings, site="after") _bind.attach( literal, leaf, deactivate=True, tag=tag.append(_tag.Tag("abjad.glissando(5)")) .append(_tag.Tag("SHOW_TO_JOIN_BROKEN_SPANNERS")) .append(_tag.Tag("RIGHT_BROKEN_SHOW_NEXT")), ) else: literal = _indicators.LilyPondLiteral(strings, site="before") _bind.attach( literal, leaf, tag=tag.append(_tag.Tag("abjad.glissando(6)")), ) if should_attach_glissando: glissando = _indicators.Glissando(zero_padding=zero_padding) glissando = _apply_tweaks(glissando, tweaks, i=i, total=total) tag_ = tag.append(_tag.Tag("abjad.glissando(7)")) if deactivate_glissando: tag_ = tag_.append(_tag.Tag("SHOW_TO_JOIN_BROKEN_SPANNERS")) _bind.attach(glissando, leaf, deactivate=deactivate_glissando, tag=tag_)
[docs]def hairpin( descriptor: str, argument: _score.Component | typing.Sequence[_score.Component], *, direction: _enums.Vertical | None = None, selector: typing.Callable = lambda _: _select.leaves(_), tag: _tag.Tag | None = None, ) -> None: r""" Attaches hairpin indicators. With three-part string descriptor: .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.hairpin("p < f", voice[:], direction=abjad.UP) >>> abjad.override(voice[0]).DynamicLineSpanner.staff_padding = 4 >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \once \override DynamicLineSpanner.staff-padding = 4 c'4 ^ \p ^ \< d'4 e'4 f'4 \f } .. container:: example With two-part string descriptor: >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.hairpin("< !", voice[:]) >>> abjad.override(voice[0]).DynamicLineSpanner.staff_padding = 4 >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \once \override DynamicLineSpanner.staff-padding = 4 c'4 \< d'4 e'4 f'4 \! } .. container:: example With dynamic objects: >>> voice = abjad.Voice("c'4 d' e' f'") >>> start_dynamic = abjad.Dynamic("niente", command=r"\!") >>> start_hairpin = abjad.StartHairpin("o<|") >>> bundle = abjad.bundle(start_hairpin, r"- \tweak color #blue") >>> stop_dynamic = abjad.Dynamic('"f"') >>> abjad.hairpin([start_dynamic, bundle, stop_dynamic], voice[:]) >>> abjad.override(voice[0]).DynamicLineSpanner.staff_padding = 4 >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \once \override DynamicLineSpanner.staff-padding = 4 c'4 \! - \tweak circled-tip ##t - \tweak stencil #abjad-flared-hairpin - \tweak color #blue \< d'4 e'4 f'4 _ #(make-dynamic-script (markup #:whiteout #:line ( #:general-align Y -2 #:normal-text #:larger "“" #:hspace -0.4 #:dynamic "f" #:hspace -0.2 #:general-align Y -2 #:normal-text #:larger "”" ) ) ) } """ indicators: list = [] start_dynamic: _dynamic.Dynamic | None hairpin: _indicators.StartHairpin | None stop_dynamic: _dynamic.Dynamic | None known_shapes = _indicators.StartHairpin("<").known_shapes if isinstance(descriptor, str): for string in descriptor.split(): if string in known_shapes: hairpin = _indicators.StartHairpin(string) indicators.append(hairpin) elif string == "!": stop_hairpin = _indicators.StopHairpin() indicators.append(stop_hairpin) else: dynamic = _dynamic.Dynamic(string) indicators.append(dynamic) else: assert isinstance(descriptor, list), repr(descriptor) indicators = descriptor start_dynamic, hairpin, stop_dynamic = None, None, None if len(indicators) == 1: if isinstance(indicators[0], _dynamic.Dynamic): start_dynamic = indicators[0] else: hairpin = indicators[0] elif len(indicators) == 2: if isinstance(indicators[0], _dynamic.Dynamic): start_dynamic = indicators[0] hairpin = indicators[1] else: hairpin = indicators[0] stop_dynamic = indicators[1] elif len(indicators) == 3: start_dynamic, hairpin, stop_dynamic = indicators else: raise Exception(indicators) if start_dynamic is not None: assert isinstance(start_dynamic, _dynamic.Dynamic), repr(start_dynamic) assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] if start_dynamic is not None: _bind.attach(start_dynamic, start_leaf, direction=direction, tag=tag) if hairpin is not None: _bind.attach(hairpin, start_leaf, direction=direction, tag=tag) if stop_dynamic is not None: _bind.attach(stop_dynamic, stop_leaf, tag=tag)
[docs]def horizontal_bracket( argument: _score.Component | typing.Sequence[_score.Component], *, selector: typing.Callable = lambda _: _select.leaves(_), start_group: _indicators.StartGroup | None = None, stop_group: _indicators.StopGroup | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches group indicators. .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.horizontal_bracket(voice[:]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startGroup d'4 e'4 f'4 \stopGroup } """ start_group = start_group or _indicators.StartGroup() stop_group = stop_group or _indicators.StopGroup() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_group, start_leaf, tag=tag) _bind.attach(stop_group, stop_leaf, tag=tag)
[docs]def ottava( argument: _score.Component | typing.Sequence[_score.Component], *, selector: typing.Callable = lambda _: _select.leaves(_), start_ottava: _indicators.Ottava = _indicators.Ottava(n=1), stop_ottava: _indicators.Ottava = _indicators.Ottava(n=0, site="after"), tag: _tag.Tag | None = None, ) -> None: r""" Attaches ottava indicators. .. container:: example >>> staff = abjad.Staff("c'4 d' e' f'") >>> abjad.ottava(staff[:]) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \ottava 1 c'4 d'4 e'4 f'4 \ottava 0 } """ assert isinstance(start_ottava, _indicators.Ottava), repr(start_ottava) assert isinstance(stop_ottava, _indicators.Ottava), repr(stop_ottava) assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_ottava, start_leaf, tag=tag) _bind.attach(stop_ottava, stop_leaf, tag=tag)
[docs]def phrasing_slur( argument: _score.Component | typing.Sequence[_score.Component], *, direction: _enums.Vertical | None = None, selector: typing.Callable = lambda _: _select.leaves(_), start_phrasing_slur: _indicators.StartPhrasingSlur | None = None, stop_phrasing_slur: _indicators.StopPhrasingSlur | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches phrasing slur indicators. .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.phrasing_slur(voice[:], direction=abjad.UP) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 ^ \( d'4 e'4 f'4 \) } """ start_phrasing_slur = _indicators.StartPhrasingSlur() stop_phrasing_slur = _indicators.StopPhrasingSlur() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] start_phrasing_slur = start_phrasing_slur or _indicators.StartPhrasingSlur() stop_phrasing_slur = stop_phrasing_slur or _indicators.StopPhrasingSlur() _bind.attach(start_phrasing_slur, start_leaf, direction=direction, tag=tag) _bind.attach(stop_phrasing_slur, stop_leaf, tag=tag)
[docs]def piano_pedal( argument: _score.Component | typing.Sequence[_score.Component], *, context: str | None = None, selector: typing.Callable = lambda _: _select.leaves(_), start_piano_pedal: _indicators.StartPianoPedal | None = None, stop_piano_pedal: _indicators.StopPianoPedal | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches piano pedal indicators. .. container:: example >>> staff = abjad.Staff("c'4 d' e' f'") >>> abjad.piano_pedal(staff[:], context="Staff") >>> abjad.setting(staff).pedalSustainStyle = "#'mixed" >>> abjad.override(staff).SustainPedalLineSpanner.staff_padding = 5 >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff \with { \override SustainPedalLineSpanner.staff-padding = 5 pedalSustainStyle = #'mixed } { c'4 \sustainOn d'4 e'4 f'4 \sustainOff } """ start_piano_pedal = start_piano_pedal or _indicators.StartPianoPedal() stop_piano_pedal = stop_piano_pedal or _indicators.StopPianoPedal() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_piano_pedal, start_leaf, context=context, tag=tag) _bind.attach(stop_piano_pedal, stop_leaf, context=context, tag=tag)
[docs]def slur( argument: _score.Component | typing.Sequence[_score.Component], *, direction: _enums.Vertical | None = None, selector: typing.Callable = lambda _: _select.leaves(_), start_slur: _indicators.StartSlur | None = None, stop_slur: _indicators.StopSlur | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches slur indicators. .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.slur(voice[:], direction=abjad.UP) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 ^ ( d'4 e'4 f'4 ) } """ start_slur = start_slur or _indicators.StartSlur() stop_slur = stop_slur or _indicators.StopSlur() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_slur, start_leaf, direction=direction, tag=tag) _bind.attach(stop_slur, stop_leaf, tag=tag)
[docs]def text_spanner( argument: _score.Component | typing.Sequence[_score.Component], *, direction: _enums.Vertical | None = None, selector: typing.Callable = lambda _: _select.leaves(_), start_text_span: _indicators.StartTextSpan | None = None, stop_text_span: _indicators.StopTextSpan | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches text span indicators. Single spanner: .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> start_text_span = abjad.StartTextSpan( ... left_text=abjad.Markup(r"\upright pont."), ... right_text=abjad.Markup(r"\markup \upright tasto"), ... style=r"\abjad-solid-line-with-arrow", ... ) >>> abjad.text_spanner( ... voice[:], direction=abjad.UP, start_text_span=start_text_span ... ) >>> abjad.override(voice[0]).TextSpanner.staff_padding = 4 >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { \once \override TextSpanner.staff-padding = 4 c'4 - \abjad-solid-line-with-arrow - \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 } - \tweak bound-details.right.text \markup \upright tasto ^ \startTextSpan d'4 e'4 f'4 \stopTextSpan } .. container:: example Enchained spanners: >>> voice = abjad.Voice("c'4 d' e' f' r") >>> start_text_span = abjad.StartTextSpan( ... left_text=abjad.Markup(r"\upright pont."), ... style=r"\abjad-dashed-line-with-arrow", ... ) >>> abjad.text_spanner(voice[:3], start_text_span=start_text_span) >>> start_text_span = abjad.StartTextSpan( ... left_text=abjad.Markup(r"\upright tasto"), ... right_text=abjad.Markup(r"\markup \upright pont."), ... style=r"\abjad-dashed-line-with-arrow", ... ) >>> abjad.text_spanner(voice[-3:], start_text_span=start_text_span) >>> abjad.override(voice).TextSpanner.staff_padding = 4 >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice \with { \override TextSpanner.staff-padding = 4 } { c'4 - \abjad-dashed-line-with-arrow - \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 } \startTextSpan d'4 e'4 \stopTextSpan - \abjad-dashed-line-with-arrow - \tweak bound-details.left.text \markup \concat { \upright tasto \hspace #0.5 } - \tweak bound-details.right.text \markup \upright pont. \startTextSpan f'4 r4 \stopTextSpan } >>> voice = abjad.Voice("c'4 d' e' f' r") >>> start_text_span = abjad.StartTextSpan( ... left_text=abjad.Markup(r"\upright pont."), ... style=r"\abjad-dashed-line-with-arrow", ... ) >>> abjad.text_spanner(voice[:3], start_text_span=start_text_span) >>> start_text_span = abjad.StartTextSpan( ... left_text=abjad.Markup(r"\upright tasto"), ... style=r"\abjad-solid-line-with-hook", ... ) >>> abjad.text_spanner(voice[-3:], start_text_span=start_text_span) >>> abjad.override(voice).TextSpanner.staff_padding = 4 >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice \with { \override TextSpanner.staff-padding = 4 } { c'4 - \abjad-dashed-line-with-arrow - \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 } \startTextSpan d'4 e'4 \stopTextSpan - \abjad-solid-line-with-hook - \tweak bound-details.left.text \markup \concat { \upright tasto \hspace #0.5 } \startTextSpan f'4 r4 \stopTextSpan } """ start_text_span = start_text_span or _indicators.StartTextSpan() stop_text_span = stop_text_span or _indicators.StopTextSpan() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_text_span, start_leaf, direction=direction, tag=tag) _bind.attach(stop_text_span, stop_leaf, tag=tag)
[docs]def tie( argument: _score.Component | typing.Sequence[_score.Component], *, direction: _enums.Vertical | None = None, repeat: bool | tuple[int, int] | typing.Callable = False, selector: typing.Callable = lambda _: _select.leaves(_), tag: _tag.Tag | None = None, ) -> None: r""" Attaches tie indicators. .. container:: example >>> staff = abjad.Staff("c'4 c' c' c'") >>> abjad.tie(staff[:], direction=abjad.UP) >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { c'4 ^ ~ c'4 ^ ~ c'4 ^ ~ c'4 } .. container:: example With repeat ties: >>> voice = abjad.Voice("c'4 c' c' c'", name="Voice") >>> abjad.tie(voice[:], repeat=True) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'4 c'4 \repeatTie c'4 \repeatTie c'4 \repeatTie } .. container:: example Removes any existing ties before attaching new tie: >>> voice = abjad.Voice("c'4 ~ c' ~ c' ~ c'", name="Voice") >>> abjad.tie(voice[:]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'4 ~ c'4 ~ c'4 ~ c'4 } .. container:: example Ties consecutive chords if all adjacent pairs have at least one pitch in common: >>> voice = abjad.Voice("<c'>4 <c' d'>4 <d'>4", name="Voice") >>> abjad.tie(voice[:]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { <c'>4 ~ <c' d'>4 ~ <d'>4 } .. container:: example Enharmonics are allowed: >>> voice = abjad.Voice("c'4 bs c' dff'", name="Voice") >>> abjad.tie(voice[:]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { c'4 ~ bs4 ~ c'4 ~ dff'4 } .. container:: example Repeat tie threshold works like this: >>> voice = abjad.Voice("d'4. d'2 d'4. d'2", name="Voice") >>> abjad.tie(voice[:], repeat=(4, 8)) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { d'4. ~ d'2 d'4. \repeatTie ~ d'2 } .. container:: example Detaches ties before attach: >>> voice = abjad.Voice("d'2 ~ d'8 ~ d'8 ~ d'8 ~ d'8", name="Voice") >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { d'2 ~ d'8 ~ d'8 ~ d'8 ~ d'8 } >>> abjad.tie(voice[:], repeat=(4, 8)) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \context Voice = "Voice" { d'2 d'8 \repeatTie ~ d'8 ~ d'8 ~ d'8 } """ if callable(repeat): pass elif repeat in (None, False): def inequality(item): return item < 0 elif repeat is True: def inequality(item): return item >= 0 else: assert isinstance(repeat, tuple) and len(repeat) == 2, repr(repeat) def inequality(item): return item >= _duration.Duration(repeat) assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) if len(leaves) < 2: raise Exception(f"must be two or more notes (not {leaves!r}).") for leaf in leaves: if not isinstance(leaf, _score.Note | _score.Chord): raise Exception(rf"tie note or chord (not {leaf!r}).") for current_leaf, next_leaf in _sequence.nwise(leaves): duration = current_leaf._get_duration() if inequality(duration): _bind.detach(_indicators.Tie, current_leaf) _bind.detach(_indicators.RepeatTie, next_leaf) repeat_tie = _indicators.RepeatTie() _bind.attach(repeat_tie, next_leaf, direction=direction, tag=tag) else: _bind.detach(_indicators.Tie, current_leaf) _bind.detach(_indicators.RepeatTie, next_leaf) tie = _indicators.Tie() _bind.attach(tie, current_leaf, direction=direction, tag=tag)
[docs]def trill_spanner( argument: _score.Component | typing.Sequence[_score.Component], *, selector: typing.Callable = lambda _: _select.leaves(_), start_trill_span: _indicators.StartTrillSpan | None = None, stop_trill_span: _indicators.StopTrillSpan | None = None, tag: _tag.Tag | None = None, ) -> None: r""" Attaches trill spanner indicators. .. container:: example >>> voice = abjad.Voice("c'4 d' e' f'") >>> abjad.trill_spanner(voice[:]) >>> abjad.show(voice) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(voice) >>> print(string) \new Voice { c'4 \startTrillSpan d'4 e'4 f'4 \stopTrillSpan } """ start_trill_span = start_trill_span or _indicators.StartTrillSpan() stop_trill_span = stop_trill_span or _indicators.StopTrillSpan() assert callable(selector) argument = selector(argument) leaves = _select.leaves(argument) start_leaf = leaves[0] stop_leaf = leaves[-1] _bind.attach(start_trill_span, start_leaf, tag=tag) _bind.attach(stop_trill_span, stop_leaf, tag=tag)