Source code for mutwo.converters.frontends.abjad_attachments

"""Build Abjad attachments from Mutwo data.
"""

import abc
import dataclasses
import typing
import warnings

import abjad  # type: ignore

from mutwo.parameters import abc as parameters_abc
from mutwo.parameters import notation_indicators
from mutwo.parameters import playing_indicators
from mutwo.utilities import tools


[docs]class AbjadAttachment(abc.ABC): """Abstract base class for all Abjad attachments."""
[docs] @classmethod def get_class_name(cls): return tools.class_name_to_object_name(cls.__name__)
[docs] @classmethod def from_indicator_collection( cls, indicator_collection: parameters_abc.IndicatorCollection ) -> typing.Optional["AbjadAttachment"]: """Initialize :class:`AbjadAttachment` from :class:`~mutwo.parameters.abc.IndicatorCollection`. If no suitable :class:`~mutwo.parameters.abc.Indicator` could be found in the collection the method will simply return None. """ class_name = cls.get_class_name() try: indicator = getattr(indicator_collection, class_name) except AttributeError: return None # typing will run a correct error: # to make this method working, we also need to inherit # the inherited subclass from a mutwo.parameters.abc.Indicator # class return cls(**indicator.get_arguments_dict()) # type: ignore
[docs] @abc.abstractmethod def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: raise NotImplementedError()
@property @abc.abstractmethod def is_active(self) -> bool: raise NotImplementedError()
[docs]class ToggleAttachment(AbjadAttachment): """Abstract base class for Abjad attachments which behave like a toggle. In Western notation one can differentiate between elements which only get notated if they change (for instance dynamics, tempo) and elements which have to be notated again and again (for instance arpeggi or tremolo). Attachments that inherit from :class:`ToggleAttachment` represent elements which only get notated if their value changes. """
[docs] @abc.abstractmethod def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> abjad.Leaf: raise NotImplementedError()
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: if previous_attachment != self: return (self.process_leaf(leaves[0], previous_attachment),) + leaves[1:] else: return leaves
[docs]class BangAttachment(AbjadAttachment): """Abstract base class for Abjad attachments which behave like a bang. In Western notation one can differentiate between elements which only get notated if they change (for instance dynamics, tempo) and elements which have to be notated again and again to be effective (for instance arpeggi or tremolo). Attachments that inherit from :class:`BangAttachment` represent elements which have to be notated again and again to be effective. """
[docs] @abc.abstractmethod def process_first_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] @abc.abstractmethod def process_last_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] @abc.abstractmethod def process_central_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: n_leaves = len(leaves) new_leaves = [] if n_leaves > 0: new_leaves.append(self.process_first_leaf(leaves[0])) if n_leaves > 2: for leaf in leaves[1:-1]: new_leaves.append(self.process_central_leaf(leaf)) if n_leaves > 1: new_leaves.append(self.process_last_leaf(leaves[-1])) return tuple(new_leaves)
[docs]class BangEachAttachment(BangAttachment):
[docs] @abc.abstractmethod def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] def process_first_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return self.process_leaf(leaf)
[docs] def process_last_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return self.process_leaf(leaf)
[docs] def process_central_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return self.process_leaf(leaf)
[docs]class BangFirstAttachment(BangAttachment):
[docs] @abc.abstractmethod def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] def process_first_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return self.process_leaf(leaf)
[docs] def process_last_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return leaf
[docs] def process_central_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return leaf
[docs]class BangLastAttachment(BangAttachment):
[docs] @abc.abstractmethod def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: raise NotImplementedError()
[docs] def process_first_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return leaf
[docs] def process_last_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return self.process_leaf(leaf)
[docs] def process_central_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: return leaf
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: n_leaves = len(leaves) if n_leaves > 0: return leaves[:-1] + (self.process_last_leaf(leaves[-1]),) else: return leaves
[docs]class Arpeggio(playing_indicators.Arpeggio, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach(abjad.Arpeggio(direction=self.direction), leaf) return leaf
[docs]class Articulation(playing_indicators.Articulation, BangEachAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach(abjad.Articulation(self.name), leaf) return leaf
[docs]class Tremolo(playing_indicators.Tremolo, BangEachAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.StemTremolo(self.n_flags * (2 ** leaf.written_duration.flag_count)), leaf, ) return leaf
[docs]class ArtificalHarmonic(playing_indicators.ArtificalHarmonic, BangEachAttachment): @staticmethod def _change_note_head_style(leaf: abjad.Chord) -> None: abjad.tweak(leaf.note_heads[1]).NoteHead.style = "#'harmonic" @staticmethod def _convert_and_test_leaf(leaf: abjad.Leaf) -> typing.Tuple[abjad.Leaf, bool]: # return True if artifical_harmonic can be attached and False if # artifical harmonic can't be attached if isinstance(leaf, abjad.Chord): try: assert len(leaf.note_heads) == 1 except AssertionError: message = ( "Can't attach artifical harmonic on chord with more or less" " than one pitch!" ) warnings.warn(message) return leaf, False return leaf, True elif isinstance(leaf, abjad.Note): new_abjad_leaf = abjad.Chord([leaf.written_pitch], leaf.written_duration,) for indicator in abjad.get.indicators(leaf): if type(indicator) != dict: abjad.attach(indicator, new_abjad_leaf) return new_abjad_leaf, True else: message = ( "Can't attach artifical harmonic on abjad leaf '{}' of type '{}'!" .format(leaf, type(leaf)) ) warnings.warn(message) return leaf, False def _get_second_pitch(self, abjad_pitch: abjad.Pitch) -> abjad.Pitch: return abjad_pitch + self.n_semitones
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: leaf, is_attachable = ArtificalHarmonic._convert_and_test_leaf(leaf) if is_attachable: first_pitch = leaf.note_heads[0].written_pitch second_pitch = self._get_second_pitch(first_pitch) leaf.written_pitches = abjad.PitchSegment([first_pitch, second_pitch]) ArtificalHarmonic._change_note_head_style(leaf) return leaf
[docs]class StringContactPoint(playing_indicators.StringContactPoint, ToggleAttachment):
[docs] def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> abjad.Leaf: string_contact_point_markup = abjad.StringContactPoint( self.contact_point ).markup if previous_attachment: if previous_attachment.contact_point == "pizzicato": # type: ignore string_contact_point_markup = abjad.Markup( [ abjad.markups.MarkupCommand( "caps", "arco {}".format( " ".join( string_contact_point_markup.contents[0].arguments ) ), ) ] ) abjad.attach( abjad.Markup( [abjad.markups.MarkupCommand("fontsize", -2.4)] + string_contact_point_markup.contents, direction="up", ), leaf, ) return leaf
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: # don't attach ordinario at start (this is the default playing technique) if previous_attachment is not None or self.contact_point != "ordinario": return super().process_leaves(leaves, previous_attachment) else: return leaves
[docs]class Pedal(playing_indicators.Pedal, ToggleAttachment):
[docs] def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> abjad.Leaf: if self.pedal_activity: pedal_class = abjad.StartPianoPedal else: pedal_class = abjad.StopPianoPedal abjad.attach(pedal_class(self.pedal_type), leaf) return leaf
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: # don't attach pedal down at start if previous_attachment is not None or self.is_active: return super().process_leaves(leaves, previous_attachment) else: return leaves
[docs]class Hairpin(playing_indicators.Hairpin, ToggleAttachment):
[docs] def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> typing.Tuple[abjad.Leaf, ...]: if self.symbol == "!": abjad.attach( abjad.StopHairpin(), leaf, ) else: abjad.attach( abjad.StartHairpin(self.symbol), leaf, ) return leaf
[docs]class BartokPizzicato(parameters_abc.ExplicitPlayingIndicator, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.LilyPondLiteral("\\snappizzicato", format_slot="absolute_after"), leaf, ) return leaf
[docs]class Fermata(playing_indicators.Fermata, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.Fermata(self.fermata_type), leaf, ) return leaf
[docs]class NaturalHarmonic(parameters_abc.ExplicitPlayingIndicator, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.LilyPondLiteral("\\flageolet", directed="up", format_slot="after"), leaf, ) return leaf
[docs]class Prall(parameters_abc.ExplicitPlayingIndicator, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.LilyPondLiteral("^\\prall", format_slot="after"), leaf, ) return leaf
[docs]class Tie(parameters_abc.ExplicitPlayingIndicator, BangLastAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.Tie(), leaf, ) return leaf
[docs]class LaissezVibrer(parameters_abc.ExplicitPlayingIndicator, BangLastAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.LaissezVibrer(), leaf, ) return leaf
[docs]class BarLine(notation_indicators.BarLine, BangLastAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.BarLine(self.abbreviation), leaf, ) return leaf
[docs]class Clef(notation_indicators.Clef, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.Clef(self.name), leaf, ) return leaf
[docs]class Ottava(notation_indicators.Ottava, ToggleAttachment):
[docs] def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> abjad.Leaf: abjad.attach( abjad.Ottava(self.n_octaves, format_slot="before"), leaf, ) return leaf
[docs] def process_leaves( self, leaves: typing.Tuple[abjad.Leaf, ...], previous_attachment: typing.Optional["AbjadAttachment"], ) -> typing.Tuple[abjad.Leaf, ...]: # don't attach ottava = 0 at start (this is the default notation) if previous_attachment is not None or self.n_octaves != 0: return super().process_leaves(leaves, previous_attachment) else: return leaves
[docs]class Markup(notation_indicators.Markup, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.Markup(self.content, direction=self.direction), leaf, ) return leaf
[docs]class RehearsalMark(notation_indicators.RehearsalMark, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach( abjad.RehearsalMark(markup=self.markup), leaf, ) return leaf
[docs]class MarginMarkup(notation_indicators.MarginMarkup, BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: command = "\\set {}.instrumentName = \\markup ".format(self.context) command += "{ " + self.content + " }" # type: ignore abjad.attach( abjad.LilyPondLiteral(command), leaf, ) return leaf
[docs]class Ornamentation(playing_indicators.Ornamentation, BangFirstAttachment): _direction_to_ornamentation_command = { "up": """ #'((moveto 0 0) (lineto 0.5 0) (curveto 0.5 0 1.5 1.75 2.5 0) (lineto 3.5 0))""", "down": """ #'((moveto 0 0) (lineto 0.5 0) (curveto 0.5 0 1.5 -1.75 2.5 0) (lineto 3.5 0))""", } def _make_markup(self) -> abjad.Markup: return abjad.Markup( [ abjad.markups.MarkupCommand("vspace", -0.25), abjad.markups.MarkupCommand("fontsize", -4), abjad.markups.MarkupCommand("rounded-box", ["{}".format(self.n_times)]), abjad.markups.MarkupCommand("hspace", -0.4), abjad.markups.MarkupCommand( "path", 0.25, abjad.LilyPondLiteral( self._direction_to_ornamentation_command[self.direction] # type: ignore ), ), ], direction="up", )
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach(self._make_markup(), leaf) return leaf
[docs]@dataclasses.dataclass() class Dynamic(ToggleAttachment): dynamic_indicator: str = "mf" # TODO(for future usage add typing.Literal)
[docs] @classmethod def from_indicator_collection( cls, indicator_collection: parameters_abc.IndicatorCollection ) -> typing.Optional["AbjadAttachment"]: """Always return None. Dynamic can't be initialised from IndicatorCollection. """ return None
@property def is_active(self) -> bool: return True
[docs] def process_leaf( self, leaf: abjad.Leaf, previous_attachment: typing.Optional["AbjadAttachment"] ) -> abjad.Leaf: abjad.attach(abjad.Dynamic(self.dynamic_indicator), leaf) return leaf
[docs]@dataclasses.dataclass() class Tempo(BangFirstAttachment): reference_duration: typing.Optional[typing.Tuple[int, int]] = (1, 4) units_per_minute: typing.Union[int, typing.Tuple[int, int], None] = 60 textual_indication: typing.Optional[str] = None # TODO(for future usage add typing.Literal['rit.', 'acc.']) dynamic_change_indication: typing.Optional[str] = None stop_dynamic_change_indicaton: bool = False print_metronome_mark: bool = True
[docs] @classmethod def from_indicator_collection( cls, indicator_collection: parameters_abc.IndicatorCollection ) -> typing.Optional["AbjadAttachment"]: """Always return None. Tempo can't be initialised from IndicatorCollection. """ return None
@property def is_active(self) -> bool: return True def _attach_metronome_mark(self, leaf: abjad.Leaf) -> None: if self.print_metronome_mark: abjad.attach( abjad.MetronomeMark( reference_duration=self.reference_duration, units_per_minute=self.units_per_minute, textual_indication=self.textual_indication, ), leaf, )
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: self._attach_metronome_mark(leaf) if self.dynamic_change_indication is not None: dynamic_change_indication = abjad.StartTextSpan( left_text=abjad.Markup(self.dynamic_change_indication) ) abjad.attach(dynamic_change_indication, leaf) return leaf
[docs]class DynamicChangeIndicationStop(BangFirstAttachment):
[docs] @classmethod def from_indicator_collection( cls, indicator_collection: parameters_abc.IndicatorCollection ) -> typing.Optional["AbjadAttachment"]: """Always return None. DynamicChangeIndicationStop can't be initialised from IndicatorCollection. """ return None
@property def is_active(self) -> bool: return True
[docs] def process_leaf(self, leaf: abjad.Leaf) -> abjad.Leaf: abjad.attach(abjad.StopTextSpan(), leaf) return leaf