Source code for abjadext.nauert.gracehandlers

import abc
import typing

import abjad

from .qevents import PitchedQEvent, QEvent, SilentQEvent


[docs]class GraceHandler(abc.ABC): """ Abstract grace-handler. Determines what pitch, if any, will be selected from a list of ``QEvents`` to be applied to an attack-point generated by a ``QGrid``, and whether there should be a ``BeforeGraceContainer`` attached to that attack-point. When called on a sequence of ``QEvents``, ``GraceHandler`` subclasses should return a pair, where the first item of the pair is a sequence of pitch tokens or ``None``, and where the second item of the pair is a ``BeforeGraceContainer`` instance or None. """ ### CLASS VARIABLES ### __slots__ = () ### INITIALIZER ### def __init__(self): pass ### SPECIAL METHODS ###
[docs] @abc.abstractmethod def __call__( self, q_events ) -> tuple[ tuple[abjad.NamedPitch, ...], typing.Optional[tuple], typing.Optional[abjad.BeforeGraceContainer], ]: """ Calls grace handler. """ raise NotImplementedError
[docs]class CollapsingGraceHandler(GraceHandler): r""" Collapsing grace-handler. Collapses pitch information into a single chord rather than creating a grace container. .. container:: example >>> durations = [1000, 1, 1, 997] >>> pitches = [0, 7, 4, 0] >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> grace_handler = nauert.CollapsingGraceHandler() >>> result = nauert.quantize( ... q_event_sequence, grace_handler=grace_handler ... ) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(result) >>> print(string) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 <c' e' g'>4 r4 r4 } } """ ### CLASS VARIABLES ### __slots__ = () ### SPECIAL METHODS ###
[docs] def __call__( self, q_events: typing.Sequence[QEvent] ) -> tuple[tuple[abjad.NamedPitch, ...], tuple, None]: """ Calls collapsing grace handler. """ pitches: list[abjad.NamedPitch] = [] attachments: list[typing.Any] = [] for q_event in q_events: if isinstance(q_event, PitchedQEvent): pitches.extend(q_event.pitches) attachments.extend(q_event.attachments) return tuple(pitches), tuple(attachments), None
[docs]class ConcatenatingGraceHandler(GraceHandler): r""" Concatenating grace-handler. Concatenates all but the final ``QEvent`` attached to a ``QGrid`` offset into a ``BeforeGraceContainer``, using a fixed leaf duration ``duration``. When called, it returns pitch information of final ``QEvent``, and the generated ``BeforeGraceContainer``, if any. .. container:: example >>> durations = [1000, 1, 999] >>> pitches = [0, 2, 0] >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> grace_handler = nauert.ConcatenatingGraceHandler() >>> result = nauert.quantize( ... q_event_sequence, grace_handler=grace_handler ... ) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> print(abjad.lilypond(result)) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 \grace { d'16 } c'4 r4 r4 } } .. container:: example When ``discard_grace_rest`` is set to ``True`` (the default), all the grace rests are discarded. >>> durations = [1000, 1, 999] >>> pitches = [0, None, 0] >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> grace_handler = nauert.ConcatenatingGraceHandler() >>> result = nauert.quantize(q_event_sequence, grace_handler=grace_handler) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(result) >>> print(string) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 c'4 r4 r4 } } .. container:: example When ``discard_grace_rest`` is set to ``False``, grace rests are not discarded. >>> grace_handler = nauert.ConcatenatingGraceHandler(discard_grace_rest=False) >>> result = nauert.quantize(q_event_sequence, grace_handler=grace_handler) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(result) >>> print(string) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 \grace { r16 } c'4 r4 r4 } } .. container:: example When ``replace_rest_with_final_grace_note`` is set to ``False``, grace notes are allowed to be attached to a rest. >>> durations = [1000, 1, 999, 1000] >>> pitches = [0, 0, None, 0] >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> grace_handler = nauert.ConcatenatingGraceHandler( ... replace_rest_with_final_grace_note=False ... ) >>> result = nauert.quantize( ... q_event_sequence, grace_handler=grace_handler ... ) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(result) >>> print(string) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 \grace { c'16 } r4 c'4 r4 } } .. container:: example When ``replace_rest_with_final_grace_note`` is set to ``True`` (the default behavior), any rest with grace notes attached to it is replaced by the last pitched grace note in the grace container. >>> grace_handler = nauert.ConcatenatingGraceHandler() >>> result = nauert.quantize(q_event_sequence, grace_handler=grace_handler) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(result) >>> print(string) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 c'4 c'4 r4 } } """ ### CLASS VARIABLES ### __slots__ = ( "_discard_grace_rest", "_grace_duration", "_replace_rest_with_final_grace_note", ) ### INITIALIZER ### def __init__( self, discard_grace_rest: bool = True, grace_duration: abjad.typings.Duration | None = None, replace_rest_with_final_grace_note: bool = True, ): self._discard_grace_rest = discard_grace_rest if grace_duration is None: grace_duration = (1, 16) grace_duration = abjad.Duration(grace_duration) assert grace_duration.is_dyadic_rational self._grace_duration = grace_duration self._replace_rest_with_final_grace_note = replace_rest_with_final_grace_note ### SPECIAL METHODS ###
[docs] def __call__( self, q_events: typing.Sequence[QEvent] ) -> tuple[ tuple[abjad.NamedPitch, ...], typing.Optional[tuple], typing.Optional[abjad.BeforeGraceContainer], ]: """ Calls concatenating grace handler. """ grace_events, final_event = q_events[:-1], q_events[-1] attachments: tuple | None if grace_events and self._replace_rest_with_final_grace_note: index = self._find_last_pitched_q_event(q_events) grace_events, final_event = q_events[:index], q_events[index] if isinstance(final_event, PitchedQEvent): # TODO: we are only supporting preserving attachments for PitchedQEvent pitches = final_event.pitches attachments = final_event.attachments else: pitches = () attachments = None grace_events_list = list(grace_events) if self._discard_grace_rest: for q_event in grace_events_list: if isinstance(q_event, SilentQEvent): grace_events_list.remove(q_event) grace_events = tuple(grace_events_list) grace_container: abjad.BeforeGraceContainer | None if grace_events: grace_container = abjad.BeforeGraceContainer() for q_event in grace_events: leaf: abjad.Leaf if isinstance(q_event, PitchedQEvent): if len(q_event.pitches) == 1: leaf = abjad.Note(q_event.pitches[0], self.grace_duration) else: leaf = abjad.Chord(q_event.pitches, self.grace_duration) else: leaf = abjad.Rest(self.grace_duration) q_event_attachments = ( None if not hasattr(q_event, "attachments") else q_event.attachments ) # assert hasattr(q_event, "attachments") # q_event_attachments = q_event.attachments if q_event_attachments is not None: abjad.annotate(leaf, "q_event_attachments", q_event_attachments) grace_container.append(leaf) else: grace_container = None return tuple(pitches), attachments, grace_container
### PRIVATE METHODS ### @staticmethod def _find_last_pitched_q_event(q_events: typing.Sequence[QEvent]) -> int: for index, q_event in enumerate(reversed(q_events)): if isinstance(q_event, PitchedQEvent): return len(q_events) - index - 1 message = "There should be at least one PitchedQEvent in q_events" raise ValueError(message) ### PUBLIC METHODS ###
[docs] def handle_orphaned_q_event_proxies(self, last_leaf, q_event_proxies): r""" Embeds orphaned ``QEvents`` into an ``AfterGraceContainer`` and attaches it to the last leaf. .. container:: example >>> durations = [1000, 1000, 1000, 400, 50, 50] >>> pitches = range(len(durations)) >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> search_tree = nauert.UnweightedSearchTree() >>> attack_point_optimizer = nauert.MeasurewiseAttackPointOptimizer() >>> q_schema = nauert.MeasurewiseQSchema( ... search_tree=search_tree, time_signature=(7, 8), use_full_measure=True ... ) >>> result = nauert.quantize( ... q_event_sequence, ... q_schema=q_schema, ... attach_tempos=True, ... attack_point_optimizer=attack_point_optimizer, ... ) >>> staff = abjad.Staff([result]) >>> score = abjad.Score([staff], name="Score") >>> abjad.show(staff) # doctest: +SKIP .. docs:: >>> string = abjad.lilypond(staff) >>> print(string) \new Staff { \new Voice { { \tempo 4=60 \time 7/8 c'4 cs'8 ~ cs'8 d'8 ~ d'8 \afterGrace ef'8 { e'16 f'16 } } } } """ grace_container = abjad.AfterGraceContainer() if q_event_proxies else None for proxy in q_event_proxies: q_event = proxy.q_event if isinstance(q_event, PitchedQEvent): if len(q_event.pitches) == 1: leaf = abjad.Note(q_event.pitches[0], self.grace_duration) else: leaf = abjad.Chord(q_event.pitches, self.grace_duration) abjad.annotate(leaf, "q_event_attachments", q_event.attachments) assert grace_container is not None grace_container.append(leaf) if grace_container: # TODO: check if the grace_container is empty? abjad.attach(grace_container, last_leaf)
### PUBLIC PROPERTIES ### @property def discard_grace_rest(self) -> bool: """ Boolean of whether to discard grace rests or not. """ return self._discard_grace_rest @property def grace_duration(self) -> abjad.Duration: """ Grace duration of concantenating grace handler. """ return self._grace_duration @property def replace_rest_with_final_grace_note(self) -> bool: """ Boolean of whether to replace the rest with the final (pitched) grace note. """ return self._replace_rest_with_final_grace_note
[docs]class DiscardingGraceHandler(GraceHandler): r""" Discarding grace-handler. Discards all but final q-event attached to an offset. Does not create grace containers. .. container:: example >>> durations = [1000, 1, 999] >>> pitches = [0, 0, 1] >>> q_event_sequence = nauert.QEventSequence.from_millisecond_pitch_pairs( ... tuple(zip(durations, pitches)) ... ) >>> grace_handler = nauert.DiscardingGraceHandler() >>> result = nauert.quantize( ... q_event_sequence, grace_handler=grace_handler ... ) >>> abjad.show(result) # doctest: +SKIP .. docs:: >>> print(abjad.lilypond(result)) \new Voice { { \tempo 4=60 %%% \time 4/4 %%% c'4 cs'4 r4 r4 } } """ ### CLASS VARIABLES ### __slots__ = () ### SPECIAL METHODS ###
[docs] def __call__( self, q_events: typing.Sequence[QEvent] ) -> tuple[tuple[abjad.NamedPitch, ...], tuple, None]: """ Calls discarding grace handler. """ q_event = q_events[-1] if isinstance(q_event, PitchedQEvent): return tuple(q_event.pitches), tuple(q_event.attachments), None return (), (), None