"""Build Abjad attachments from Mutwo data.
"""
import dataclasses
import inspect
import typing
import warnings
import abjad # type: ignore
from mutwo import abjad_parameters
from mutwo import music_parameters
LeafOrLeafSequence = typing.Union[abjad.Leaf, typing.Sequence[abjad.Leaf]]
[docs]class Arpeggio(music_parameters.Arpeggio, abjad_parameters.abc.BangFirstAttachment):
_string_to_direction = {
"up": abjad.enums.Up,
"down": abjad.enums.Down,
"center": abjad.enums.Center,
"^": abjad.enums.Up,
"_": abjad.enums.Down,
}
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
direction = self.direction
if direction in self._string_to_direction:
direction = self._string_to_direction[direction]
abjad.attach(abjad.Arpeggio(direction=direction), leaf)
return leaf
[docs]class Articulation(
music_parameters.Articulation, abjad_parameters.abc.BangEachAttachment
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(abjad.Articulation(self.name), leaf)
return leaf
[docs]class Trill(music_parameters.Trill, abjad_parameters.abc.BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(abjad.Articulation("trill"), leaf)
return leaf
[docs]class Cue(music_parameters.Cue, abjad_parameters.abc.BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.Markup(contents=f"\\rounded-box {{ {self.nth_cue} }}", direction="^"),
leaf,
)
return leaf
[docs]class WoodwindFingering(
music_parameters.WoodwindFingering, abjad_parameters.abc.BangFirstAttachment
):
fingering_size = 0.7
@staticmethod
def _tuple_to_scheme_list(tuple_to_convert: tuple[str, ...]) -> str:
return f"({' '.join(tuple_to_convert)})"
def _get_markup_content(self) -> str:
# \\override #'(graphical . #f)
return f"""
\\override #'(size . {self.fingering_size})
{{
\\woodwind-diagram
#'{self.instrument}
#'((cc . {self._tuple_to_scheme_list(self.cc)})
(lh . {self._tuple_to_scheme_list(self.left_hand)})
(rh . {self._tuple_to_scheme_list(self.right_hand)}))
}}"""
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
fingering = abjad.LilyPondLiteral(
f"^\\markup {self._get_markup_content()}", format_slot="after"
)
abjad.attach(fingering, leaf)
return leaf
[docs]class Tremolo(music_parameters.Tremolo, abjad_parameters.abc.BangEachAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.StemTremolo(self.n_flags * (2 ** leaf.written_duration.flag_count)),
leaf,
)
return leaf
[docs]class ArtificalHarmonic(
music_parameters.ArtificalHarmonic, abjad_parameters.abc.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) -> 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) -> LeafOrLeafSequence:
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 PreciseNaturalHarmonic(
music_parameters.PreciseNaturalHarmonic, abjad_parameters.abc.BangEachAttachment
):
@staticmethod
def _convert_leaf(leaf: abjad.Leaf) -> tuple[abjad.Leaf, bool]:
if isinstance(leaf, abjad.Chord):
return leaf
else:
new_abjad_leaf = abjad.Chord(
"c",
leaf.written_duration,
)
for indicator in abjad.get.indicators(leaf):
if type(indicator) != dict:
abjad.attach(indicator, new_abjad_leaf)
return new_abjad_leaf
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
if isinstance(leaf, (abjad.Chord, abjad.Note)):
leaf = PreciseNaturalHarmonic._convert_leaf(leaf)
leaf.written_pitches = abjad.PitchSegment(
[
self.string_pitch,
abjad.NamedPitch(
self.string_pitch.name,
octave=self.string_pitch.octave.number + 1,
),
]
)
leaf.note_heads[1]._written_pitch = self.played_pitch
if self.harmonic_note_head_style:
ArtificalHarmonic._change_note_head_style(leaf)
if self.parenthesize_lower_note_head:
leaf.note_heads[0].is_parenthesized = True
return leaf
[docs]class Pedal(music_parameters.Pedal, abjad_parameters.abc.ToggleAttachment):
[docs] def process_leaf(
self,
leaf: abjad.Leaf,
previous_attachment: typing.Optional["abjad_parameters.abc.AbjadAttachment"],
) -> LeafOrLeafSequence:
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_leaf_tuple(
self,
leaf_tuple: tuple[abjad.Leaf, ...],
previous_attachment: typing.Optional["abjad_parameters.abc.AbjadAttachment"],
) -> tuple[abjad.Leaf, ...]:
# don't attach pedal down at start
if previous_attachment is not None or self.is_active:
return super().process_leaf_tuple(leaf_tuple, previous_attachment)
else:
return leaf_tuple
[docs]class Hairpin(music_parameters.Hairpin, abjad_parameters.abc.ToggleAttachment):
niente_literal = abjad.LilyPondLiteral(r"\once \override Hairpin.circled-tip = ##t")
[docs] def process_leaf(
self,
leaf: abjad.Leaf,
_: typing.Optional["abjad_parameters.abc.AbjadAttachment"],
) -> LeafOrLeafSequence:
if self.symbol == "!":
abjad.attach(
abjad.StopHairpin(),
leaf,
)
else:
if self.niente:
abjad.attach(self.niente_literal, leaf)
abjad.attach(
abjad.StartHairpin(self.symbol),
leaf,
)
return leaf
def _process_espressivo(
self,
leaf_tuple: tuple[abjad.Leaf, ...],
previous_attachment: typing.Optional[abjad_parameters.abc.AbjadAttachment],
) -> tuple[abjad.Leaf, ...]:
leaf_count = len(leaf_tuple)
if leaf_count >= 2:
crescendo_leaf = leaf_tuple[0]
if self.niente:
abjad.attach(self.niente_literal, crescendo_leaf)
abjad.attach(abjad.StartHairpin("<"), crescendo_leaf)
decrescendo_leaf = leaf_tuple[int(len(leaf_tuple) // 2)]
if self.niente:
abjad.attach(self.niente_literal, decrescendo_leaf)
abjad.attach(abjad.StartHairpin(">"), decrescendo_leaf)
elif leaf_count == 1:
abjad.attach(abjad.Articulation('espressivo'), leaf_tuple[0])
return leaf_tuple
[docs] def process_leaf_tuple(
self,
leaf_tuple: tuple[abjad.Leaf, ...],
previous_attachment: typing.Optional[abjad_parameters.abc.AbjadAttachment],
) -> tuple[abjad.Leaf, ...]:
if self.symbol == "<>":
return self._process_espressivo(leaf_tuple, previous_attachment)
else:
return super().process_leaf_tuple(leaf_tuple, previous_attachment)
[docs]class BartokPizzicato(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangFirstAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LilyPondLiteral("\\snappizzicato", format_slot="after"),
leaf,
)
return leaf
[docs]class BreathMark(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangFirstAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LilyPondLiteral("\\breathe", format_slot="before"),
leaf,
)
return leaf
[docs]class Fermata(music_parameters.Fermata, abjad_parameters.abc.BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.Fermata(self.fermata_type),
leaf,
)
return leaf
[docs]class NaturalHarmonic(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangFirstAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LilyPondLiteral("\\flageolet", directed="up", format_slot="after"),
leaf,
)
return leaf
[docs]class Prall(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangFirstAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LilyPondLiteral("^\\prall", format_slot="after"),
leaf,
)
return leaf
[docs]class Tie(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangLastAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
if isinstance(leaf, (abjad.Chord, abjad.Note)):
abjad.attach(
abjad.Tie(),
leaf,
)
return leaf
[docs]class DurationLineTriller(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangEachAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
if isinstance(leaf, (abjad.Chord, abjad.Note)):
abjad.attach(
abjad.LilyPondLiteral("\\once \\override DurationLine.style = #'trill"),
leaf,
)
return leaf
[docs]class DurationLineDashed(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangEachAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
if isinstance(leaf, (abjad.Chord, abjad.Note)):
abjad.attach(
abjad.LilyPondLiteral(
"\\once \\override DurationLine.style = #'dashed-line"
),
leaf,
)
return leaf
[docs]class Glissando(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangLastAttachment,
):
thickness = 3
minimum_length = 5
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LilyPondLiteral(
"\\override Glissando.thickness = #'{}".format(self.thickness)
),
leaf,
)
abjad.attach(
abjad.LilyPondLiteral(
"\\override Glissando.minimum-length = #{}".format(self.minimum_length)
),
leaf,
)
abjad.attach(
abjad.LilyPondLiteral("\\override Glissando.breakable = ##t"),
leaf,
)
abjad.attach(
abjad.LilyPondLiteral("\\override Glissando.after-line-breaking = ##t"),
leaf,
)
# Prevent duration line from getting printed when we print a glissando
abjad.attach(
abjad.LilyPondLiteral("\\once \\override DurationLine.style = #'none"), leaf
)
command = "\\override "
command += "Glissando.springs-and-rods = #ly:spanner::set-spacing-rods"
abjad.attach(abjad.LilyPondLiteral(command), leaf)
abjad.attach(
abjad.Glissando(allow_ties=True),
leaf,
)
return leaf
[docs]class BendAfter(music_parameters.BendAfter, abjad_parameters.abc.BangLastAttachment):
def _attach_bend_after_to_note(self, note: abjad.Note):
abjad.attach(
abjad.LilyPondLiteral(
"\\once \\override BendAfter.thickness = #'{}".format(self.thickness)
),
note,
)
abjad.attach(
abjad.LilyPondLiteral(
f"\\once \\override BendAfter.minimum-length = #{self.minimum_length}"
),
note,
)
abjad.attach(
abjad.LilyPondLiteral("\\once \\override DurationLine.style = #'none"), note
)
abjad.attach(abjad.BendAfter(bend_amount=self.bend_amount), note)
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
if isinstance(leaf, abjad.Chord):
indicator_list = abjad.get.indicators(leaf)
container = abjad.Container([], simultaneous=True)
for note_head in leaf.note_heads:
note = abjad.Note("c", leaf.written_duration)
note.note_head._written_pitch = note_head.written_pitch
self._attach_bend_after_to_note(note)
for indicator in indicator_list:
abjad.attach(indicator, note)
voice = abjad.Voice([note])
container.append(voice)
return container
elif isinstance(leaf, abjad.Note):
self._attach_bend_after_to_note(leaf)
return leaf
[docs]class LaissezVibrer(
music_parameters.abc.ExplicitPlayingIndicator,
abjad_parameters.abc.BangLastAttachment,
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.LaissezVibrer(),
leaf,
)
return leaf
[docs]class BarLine(music_parameters.BarLine, abjad_parameters.abc.BangLastAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.BarLine(self.abbreviation),
leaf,
)
return leaf
[docs]class Clef(music_parameters.Clef, abjad_parameters.abc.BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.Clef(self.name),
leaf,
)
return leaf
[docs]class Ottava(music_parameters.Ottava, abjad_parameters.abc.ToggleAttachment):
[docs] def process_leaf(
self,
leaf: abjad.Leaf,
previous_attachment: typing.Optional["abjad_parameters.abc.AbjadAttachment"],
) -> LeafOrLeafSequence:
abjad.attach(
abjad.Ottava(self.n_octaves, format_slot="before"),
leaf,
)
return leaf
[docs] def process_leaf_tuple(
self,
leaf_tuple: tuple[abjad.Leaf, ...],
previous_attachment: typing.Optional["abjad_parameters.abc.AbjadAttachment"],
) -> 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_leaf_tuple(leaf_tuple, previous_attachment)
else:
return leaf_tuple
[docs]class Markup(music_parameters.Markup, abjad_parameters.abc.BangFirstAttachment):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.Markup(self.content, direction=self.direction),
leaf,
)
return leaf
[docs]class RehearsalMark(
music_parameters.RehearsalMark, abjad_parameters.abc.BangFirstAttachment
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(
abjad.RehearsalMark(markup=self.markup),
leaf,
)
return leaf
[docs]class MarginMarkup(
music_parameters.MarginMarkup, abjad_parameters.abc.BangFirstAttachment
):
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
command = "\\set {}.instrumentName = \\markup ".format(self.context)
command += "{ " + self.content + " }" # type: ignore
abjad.attach(
abjad.LilyPondLiteral(command),
leaf,
)
return leaf
[docs]class Ornamentation(
music_parameters.Ornamentation, abjad_parameters.abc.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) -> LeafOrLeafSequence:
abjad.attach(self._make_markup(), leaf)
return leaf
[docs]@dataclasses.dataclass()
class Dynamic(abjad_parameters.abc.ToggleAttachment):
dynamic_indicator: str = "mf" # TODO(for future usage add typing.Literal)
[docs] @classmethod
def from_indicator_collection(
cls, indicator_collection: music_parameters.abc.IndicatorCollection
) -> typing.Optional["abjad_parameters.abc.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["abjad_parameters.abc.AbjadAttachment"],
) -> LeafOrLeafSequence:
abjad.attach(abjad.Dynamic(self.dynamic_indicator), leaf)
return leaf
[docs]@dataclasses.dataclass()
class Tempo(abjad_parameters.abc.BangFirstAttachment):
reference_duration: typing.Optional[tuple[int, int]] = (1, 4)
units_per_minute: typing.Union[int, 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: music_parameters.abc.IndicatorCollection
) -> typing.Optional["abjad_parameters.abc.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) -> LeafOrLeafSequence:
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(abjad_parameters.abc.BangFirstAttachment):
[docs] @classmethod
def from_indicator_collection(
cls, indicator_collection: music_parameters.abc.IndicatorCollection
) -> typing.Optional["abjad_parameters.abc.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) -> LeafOrLeafSequence:
abjad.attach(abjad.StopTextSpan(), leaf)
return leaf
[docs]class GraceNoteSequentialEvent(abjad_parameters.abc.BangFirstAttachment):
def __init__(self, grace_note_sequential_event: abjad.BeforeGraceContainer):
self._grace_note_sequential_event = grace_note_sequential_event
[docs] @classmethod
def from_indicator_collection(
cls, indicator_collection: music_parameters.abc.IndicatorCollection
) -> typing.Optional["abjad_parameters.abc.AbjadAttachment"]:
"""Always return None.
GraceNoteSequentialEvent can't be initialised from IndicatorCollection.
"""
return None
@property
def is_active(self) -> bool:
return True
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
for (
indicator_to_detach
) in (
abjad_parameters.constants.INDICATORS_TO_DETACH_FROM_MAIN_LEAF_AT_GRACE_NOTES_TUPLE
):
detached_indicator = abjad.detach(indicator_to_detach, leaf)
abjad.attach(detached_indicator, self._grace_note_sequential_event[0])
abjad.attach(self._grace_note_sequential_event, leaf)
return leaf
[docs]class AfterGraceNoteSequentialEvent(abjad_parameters.abc.BangLastAttachment):
def __init__(self, after_grace_note_sequential_event: abjad.AfterGraceContainer):
self._after_grace_note_sequential_event = after_grace_note_sequential_event
[docs] @classmethod
def from_indicator_collection(
cls, indicator_collection: music_parameters.abc.IndicatorCollection
) -> typing.Optional["abjad_parameters.abc.AbjadAttachment"]:
"""Always return None.
AfterGraceNoteSequentialEvent can't be initialised from IndicatorCollection.
"""
return None
@property
def is_active(self) -> bool:
return True
[docs] def process_leaf(self, leaf: abjad.Leaf) -> LeafOrLeafSequence:
abjad.attach(self._after_grace_note_sequential_event, leaf)
return leaf
# Auto define all due to many classes
__all__ = tuple(
name
for name, cls in globals().items()
if inspect.isclass(cls)
and not inspect.isabstract(cls)
and abjad_parameters.abc.AbjadAttachment in inspect.getmro(cls)
)