Source code for abjad.obgc

import typing

from . import _indentlib, _iterlib
from . import bind as _bind
from . import duration as _duration
from . import get as _get
from . import indicators as _indicators
from . import iterate as _iterate
from . import mutate as _mutate
from . import parentage as _parentage
from . import score as _score
from . import select as _select
from . import spanners as _spanners
from . import tag as _tag
from . import tweaks as _tweaks
from . import typings as _typings


[docs]class OnBeatGraceContainer(_score.Container): r""" On-beat grace container. .. note:: On-beat grace containers must be included in a named voice. .. container:: example On-beat grace containers implement custom formatting not available in LilyPond: >>> music_voice = abjad.Voice("c'4 d'4 e'4 f'4", name="MusicVoice") >>> string = "<d' g'>8 a' b' c'' d'' c'' b' a' b' c'' d''" >>> container = abjad.on_beat_grace_container( ... string, music_voice[1:3], grace_leaf_duration=(1, 24) ... ) >>> abjad.attach(abjad.Articulation(">"), container[0]) >>> staff = abjad.Staff([music_voice]) >>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { c'4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceOne < \tweak font-size 0 \tweak transparent ##t d' g' >8 * 1/3 - \accent [ ( a'8 * 1/3 b'8 * 1/3 c''8 * 1/3 d''8 * 1/3 c''8 * 1/3 b'8 * 1/3 a'8 * 1/3 b'8 * 1/3 c''8 * 1/3 d''8 * 1/3 ) ] } \context Voice = "MusicVoice" { \voiceTwo d'4 e'4 } >> \oneVoice f'4 } } """ ### CLASS VARIABLES ### __slots__ = ("_grace_leaf_duration",) ### INITIALIZER ### def __init__( self, components: str | typing.Sequence[_score.Leaf] = (), *, grace_leaf_duration: _typings.Duration | None = None, identifier: str | None = None, name: str | None = None, tag: _tag.Tag | None = None, ) -> None: super().__init__(components, identifier=identifier, name=name, tag=tag) if grace_leaf_duration is not None: grace_leaf_duration = _duration.Duration(grace_leaf_duration) self._grace_leaf_duration = grace_leaf_duration ### SPECIAL METHODS ### def __getnewargs__(self): """ Gets new after grace container arguments. Returns tuple of single empty list. """ return ([],) ### PRIVATE METHODS ### def _attach_lilypond_one_voice(self): anchor_leaf = self.get_anchor_leaf() anchor_voice = _parentage.Parentage(anchor_leaf).get(_score.Voice) final_anchor_leaf = _iterlib._get_leaf(anchor_voice, -1) next_leaf = _iterlib._get_leaf(final_anchor_leaf, 1) if next_leaf is None: return command = _indicators.VoiceNumber() if _get.has_indicator(next_leaf, command): return next_leaf_parent = _get.parentage(next_leaf).parent if isinstance(next_leaf_parent, OnBeatGraceContainer): return if self._is_on_beat_anchor_voice(next_leaf_parent): return tag = self.tag tag = tag.append( _tag.Tag("abjad.OnBeatGraceContainer._attach_lilypond_one_voice()") ) tag = tag.append(_tag.Tag("ONE_VOICE_COMMAND")) _bind.attach(command, next_leaf, tag=tag) def _format_invocation(self): return r'\context Voice = "On_Beat_Grace_Container"' def _format_open_brackets_site(self, contributions): result = [] if self.identifier: open_bracket = f"{{ {self.identifier}" else: open_bracket = "{" brackets_open = [open_bracket] overrides = contributions.grob_overrides settings = contributions.context_settings if overrides or settings: contributions = [self._format_invocation(), r"\with", "{"] contributions = self._tag_strings(contributions) result.extend(contributions) contributions = [_indentlib.INDENT + _ for _ in overrides] contributions = self._tag_strings(contributions) result.extend(contributions) contributions = [_indentlib.INDENT + _ for _ in settings] contributions = self._tag_strings(contributions) result.extend(contributions) contributions = [f"}} {brackets_open[0]}"] contributions = ["}", open_bracket] contributions = self._tag_strings(contributions) result.extend(contributions) else: contribution = self._format_invocation() contribution += f" {brackets_open[0]}" contributions = [contribution] contributions = [self._format_invocation(), open_bracket] contributions = self._tag_strings(contributions) result.extend(contributions) return result @staticmethod def _is_on_beat_anchor_voice(container): wrapper = _get.parentage(container).parent if wrapper is None: return False if not isinstance(container, _score.Voice): return False return OnBeatGraceContainer._is_on_beat_wrapper(wrapper) @staticmethod def _is_on_beat_wrapper(container): if not container.simultaneous: return False if len(container) != 2: return False if isinstance(container[0], OnBeatGraceContainer) and isinstance( container[1], _score.Voice ): return True if isinstance(container[0], _score.Voice) and isinstance( container[1], OnBeatGraceContainer ): return True return False def _match_anchor_leaf(self): string = "abjad.OnBeatGraceContainer._match_anchor_leaf()" tag = self.tag.append(_tag.Tag(string)) first_grace = _iterlib._get_leaf(self, 0) if not isinstance(first_grace, _score.Note | _score.Chord): message = "must start with note or chord:\n" message += f" {repr(self)}" raise Exception(message) anchor_leaf = self.get_anchor_leaf() if not isinstance(anchor_leaf, _score.Note | _score.Chord): return if not isinstance(first_grace, _score.Note | _score.Chord): return if isinstance(first_grace, _score.Note): chord = _score.Chord(first_grace, tag=tag) _mutate.replace(first_grace, chord) first_grace = chord generator = _iterate.pitches(anchor_leaf) anchor_pitches = list(generator) highest_pitch = list(sorted(anchor_pitches))[-1] if highest_pitch not in first_grace.note_heads: first_grace.note_heads.append(highest_pitch) grace_mate_head = first_grace.note_heads.get(highest_pitch) _tweaks.tweak(grace_mate_head, r"\tweak font-size 0", tag=tag) _tweaks.tweak(grace_mate_head, r"\tweak transparent ##t", tag=tag) def _set_leaf_durations(self): if self.grace_leaf_duration is None: return for leaf in _select.leaves(self): duration = _get.duration(leaf) if duration != self.grace_leaf_duration: multiplier = self.grace_leaf_duration / duration leaf.multiplier = _duration.pair(multiplier) ### PUBLIC PROPERTIES ### @property def grace_leaf_duration(self) -> _duration.Duration | None: """ Gets grace leaf duration. """ return self._grace_leaf_duration ### PUBLIC METHODS ###
[docs] def get_anchor_leaf(self): """ Gets anchor leaf. """ container = _get.parentage(self).parent if container is None: return None if len(container) != 2: raise Exception("Combine on-beat grace container with one other voice.") if container.index(self) == 0: anchor_voice = container[-1] else: assert container.index(self) == 1 anchor_voice = container[0] anchor_leaf = _select.leaf(anchor_voice, 0, grace=False) return anchor_leaf
[docs]def on_beat_grace_container( grace_leaves: str | typing.Sequence[_score.Leaf], nongrace_leaves: typing.Sequence[_score.Leaf], *, do_not_attach_one_voice_command: bool = False, do_not_beam: bool = False, do_not_slash: bool = False, do_not_slur: bool = False, grace_font_size: int = -3, grace_leaf_duration: _typings.Duration | None = None, grace_polyphony_command: _indicators.VoiceNumber = _indicators.VoiceNumber(1), nongrace_polyphony_command: _indicators.VoiceNumber = _indicators.VoiceNumber(2), tag: _tag.Tag = _tag.Tag(), ) -> "OnBeatGraceContainer": r""" Makes on-beat grace container (with ``grace_leaves``) and attaches to ``nongrace_leaves``. .. container:: example >>> def make_lilypond_file(anchor_voice_string, obgc_string, *, below=False): ... music_voice = abjad.Voice(anchor_voice_string, name="MusicVoice") ... if below is False: ... nongrace_polyphony_command = abjad.VoiceNumber(2) ... grace_polyphony_command = abjad.VoiceNumber(1) ... else: ... nongrace_polyphony_command = abjad.VoiceNumber(1) ... grace_polyphony_command = abjad.VoiceNumber(2) ... result = abjad.on_beat_grace_container( ... obgc_string, ... music_voice[1:3], ... grace_leaf_duration=abjad.Duration(1, 30), ... grace_polyphony_command=grace_polyphony_command, ... nongrace_polyphony_command=nongrace_polyphony_command, ... ) ... staff = abjad.Staff([music_voice]) ... lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', staff]) ... return lilypond_file .. container:: example GRACE NOTES ABOVE. Note-to-note anchor: >>> lilypond_file = make_lilypond_file( ... "c'4 d' e' f'", ... "g'8 a' b' c'' d'' c'' b' a' b' c'' d''", ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { c'4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceOne < \tweak font-size 0 \tweak transparent ##t d' g' >8 * 4/15 [ ( a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 c''8 * 4/15 b'8 * 4/15 a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceTwo d'4 e'4 } >> \oneVoice f'4 } } Note-to-chord anchor: >>> lilypond_file = make_lilypond_file( ... "<a c'>4 <b d'> <c' e'> <d' f'>", ... "g'8 a' b' c'' d'' c'' b' a' b' c'' d''", ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { <a c'>4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceOne < \tweak font-size 0 \tweak transparent ##t d' g' >8 * 4/15 [ ( a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 c''8 * 4/15 b'8 * 4/15 a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceTwo <b d'>4 <c' e'>4 } >> \oneVoice <d' f'>4 } } Chord-to-note anchor: >>> lilypond_file = make_lilypond_file( ... "c'4 d' e' f'", ... "<g' b'>8 a' b' c'' d'' c'' b' a' b' c'' d''", ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { c'4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceOne < \tweak font-size 0 \tweak transparent ##t d' g' b' >8 * 4/15 [ ( a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 c''8 * 4/15 b'8 * 4/15 a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceTwo d'4 e'4 } >> \oneVoice f'4 } } Chord-to-chord anchor: >>> lilypond_file = make_lilypond_file( ... "<a c'>4 <b d'> <c' e'> <d' f'>", ... "<g' b'>8 a' b' c'' d'' c'' b' a' b' c'' d''", ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { <a c'>4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceOne < \tweak font-size 0 \tweak transparent ##t d' g' b' >8 * 4/15 [ ( a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 c''8 * 4/15 b'8 * 4/15 a'8 * 4/15 b'8 * 4/15 c''8 * 4/15 d''8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceTwo <b d'>4 <c' e'>4 } >> \oneVoice <d' f'>4 } } .. container:: example GRACE NOTES BELOW. Note-to-note anchor: >>> lilypond_file = make_lilypond_file( ... "c'4 d' e' f'", ... "g8 a b c' d' c' b a b c' d'", ... below=True, ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { c'4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceTwo < g \tweak font-size 0 \tweak transparent ##t d' >8 * 4/15 [ ( a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 c'8 * 4/15 b8 * 4/15 a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceOne d'4 e'4 } >> \oneVoice f'4 } } Note-to-chord anchor: >>> lilypond_file = make_lilypond_file( ... "<c' e'>4 <d' f'> <e' g'> <f' a'>", ... "g8 a b c' d' c' b a b c' d'", ... below=True, ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { <c' e'>4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceTwo < g \tweak font-size 0 \tweak transparent ##t f' >8 * 4/15 [ ( a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 c'8 * 4/15 b8 * 4/15 a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceOne <d' f'>4 <e' g'>4 } >> \oneVoice <f' a'>4 } } Chord-to-note anchor: >>> lilypond_file = make_lilypond_file( ... "c'4 d' e' f'", ... "<e g>8 a b c' d' c' b a b c' d'", ... below=True, ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { c'4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceTwo < e g \tweak font-size 0 \tweak transparent ##t d' >8 * 4/15 [ ( a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 c'8 * 4/15 b8 * 4/15 a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceOne d'4 e'4 } >> \oneVoice f'4 } } Chord-to-chord anchor: >>> lilypond_file = make_lilypond_file( ... "<c' e'>4 <d' f'> <e' g'> <f' a'>", ... "<e g>8 a b c' d' c' b a b c' d'", ... below=True, ... ) >>> abjad.show(lilypond_file) # doctest: +SKIP .. docs:: >>> staff = lilypond_file.items[-1] >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \context Voice = "MusicVoice" { <c' e'>4 << \context Voice = "On_Beat_Grace_Container" { \set fontSize = #-3 \slash \voiceTwo < e g \tweak font-size 0 \tweak transparent ##t f' >8 * 4/15 [ ( a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 c'8 * 4/15 b8 * 4/15 a8 * 4/15 b8 * 4/15 c'8 * 4/15 d'8 * 4/15 ) ] } \context Voice = "MusicVoice" { \voiceOne <d' f'>4 <e' g'>4 } >> \oneVoice <f' a'>4 } } .. TODO:: Fix stem-alignment in final example. """ if not isinstance(grace_leaves, str): assert all(isinstance(_, _score.Leaf) for _ in grace_leaves), repr(grace_leaves) assert all(isinstance(_, _score.Leaf) for _ in nongrace_leaves), repr( nongrace_leaves ) assert isinstance(grace_font_size, int), repr(grace_font_size) assert isinstance(grace_polyphony_command, _indicators.VoiceNumber), repr( grace_polyphony_command ) assert isinstance(nongrace_polyphony_command, _indicators.VoiceNumber), repr( nongrace_polyphony_command ) assert isinstance(tag, _tag.Tag), repr(tag) tag = tag.append(_tag.Tag("abjad.on_beat_grace_container()")) if not _mutate._are_contiguous_same_parent( nongrace_leaves, ignore_before_after_grace=True ): message = "nongrace leaves must be contiguous in same parent:\n" message += f" {repr(nongrace_leaves)}" raise Exception(message) on_beat_grace_container = OnBeatGraceContainer( grace_leaves, grace_leaf_duration=grace_leaf_duration, tag=tag ) anchor_leaf = _iterlib._get_leaf(nongrace_leaves, 0) anchor_voice = _parentage.Parentage(anchor_leaf).get(_score.Voice) assert isinstance(anchor_voice, _score.Voice), repr(anchor_voice) if anchor_voice.name is None: raise Exception(f"anchor voice must be named:\n {repr(anchor_voice)}") anchor_voice_insert = _score.Voice(name=anchor_voice.name, tag=tag) _mutate.wrap(nongrace_leaves, anchor_voice_insert) container = _score.Container(simultaneous=True, tag=tag) _mutate.wrap(anchor_voice_insert, container) container.insert(0, on_beat_grace_container) on_beat_grace_container._match_anchor_leaf() on_beat_grace_container._set_leaf_durations() insert_duration = _get.duration(anchor_voice_insert) grace_container_duration = _get.duration(on_beat_grace_container) if insert_duration < grace_container_duration: message = f"grace {repr(grace_container_duration)}" message += f" exceeds anchor {repr(insert_duration)}." raise Exception(message) literal = _indicators.LilyPondLiteral( rf"\set fontSize = #{grace_font_size}", site="before", ) _bind.attach(literal, on_beat_grace_container[0], tag=tag) if not do_not_beam: _spanners.beam(on_beat_grace_container[:], tag=tag) if not do_not_slash: literal = _indicators.LilyPondLiteral(r"\slash", site="before") _bind.attach(literal, on_beat_grace_container[0], tag=tag) if not do_not_slur: _spanners.slur(on_beat_grace_container[:], tag=tag) first_grace = _iterlib._get_leaf(on_beat_grace_container, 0) _bind.detach(_indicators.VoiceNumber(), anchor_leaf) _bind.attach( grace_polyphony_command, first_grace, tag=tag, ) _bind.detach(_indicators.VoiceNumber(), anchor_leaf) _bind.attach( nongrace_polyphony_command, anchor_leaf, tag=tag, ) if not do_not_attach_one_voice_command: last_anchor_leaf = _iterlib._get_leaf(nongrace_leaves, -1) next_leaf = _iterlib._get_leaf(last_anchor_leaf, 1) if next_leaf is not None: command = _indicators.VoiceNumber() _bind.attach(command, next_leaf, tag=tag) return on_beat_grace_container