import functools
import abjad
[docs]@functools.total_ordering
class BowAnglePoint:
"""
Bow Angle Point
.. container:: example
>>> point = evans.BowAnglePoint(90)
>>> point
BowAnglePoint(degrees=NonreducedFraction(90, 90))
"""
def __init__(
self,
degrees=None,
):
if degrees is not None:
self._degrees = abjad.NonreducedFraction(degrees, 90)
else:
self._degrees = degrees
[docs] def __lt__(self, argument) -> bool:
self_degrees = self.degrees or 0
argument_degrees = argument.degrees or 0
return self_degrees < argument_degrees
[docs] def __repr__(self):
return abjad.StorageFormatManager(self).get_repr_format()
[docs] def __str__(self):
return str(self.markup)
@property
def degrees(self):
return self._degrees
@property
def markup(self):
if self._degrees is None:
degrees = 0
else:
degrees = self.degrees.numerator
symbol = (
r"\concat { \translate #'(0 . 0) "
+ f"{degrees}"
+ r" \translate #'(0 . 1) \teeny o \hspace #0.5 }"
)
string = rf"\markup {{ \vcenter {symbol} }}"
markup = abjad.Markup(string, literal=True)
return markup
[docs]def bow_angle_spanner(
argument,
*,
omit_bow_changes=True,
tag=None,
):
r"""
.. container:: example
>>> point_1 = evans.BowAnglePoint(-45)
>>> tech_1 = abjad.BowMotionTechnique("ordinario")
>>> point_2 = evans.BowAnglePoint(45)
>>> tech_2 = abjad.BowMotionTechnique("ordinario")
>>> staff = abjad.Staff("c'2 c'2")
>>> abjad.attach(point_1, staff[0])
>>> abjad.attach(tech_1, staff[0])
>>> abjad.attach(point_2, staff[1])
>>> abjad.attach(tech_2, staff[1])
>>> evans.bow_angle_spanner(staff)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> print(abjad.lilypond(staff))
\new Staff
{
\tweak Y-offset #-4.0
\tweak stencil #ly:text-interface::print
\tweak text \markup { \vcenter \concat { \translate #'(0 . 0) -45 \translate #'(0 . 1) \teeny o \hspace #0.5 } }
c'2
- \tweak style #'line
\glissando
\tweak Y-offset #0.0
\tweak stencil #ly:text-interface::print
\tweak text \markup { \vcenter \concat { \translate #'(0 . 0) 45 \translate #'(0 . 1) \teeny o \hspace #0.5 } }
c'2
}
"""
def _get_indicators(leaf):
bow_contact_point = None
prototype = BowAnglePoint
if leaf._has_indicator(prototype):
bow_contact_point = leaf._get_indicators(prototype)[0]
bow_motion_technique = None
prototype = abjad.BowMotionTechnique
if leaf._has_indicator(prototype):
bow_motion_technique = leaf._get_indicators(prototype)[0]
return (bow_contact_point, bow_motion_technique)
def _make_bow_contact_point_tweaks(leaf, bow_contact_point):
if bow_contact_point is None:
return
abjad.tweak(leaf.note_head).stencil = "#ly:text-interface::print"
abjad.tweak(leaf.note_head).text = bow_contact_point.markup
y_offset = float((4 * bow_contact_point.degrees) - 2)
abjad.tweak(leaf.note_head).Y_offset = f"#{y_offset}"
def _make_bow_change_contributions(leaf, leaves, bow_contact_point):
cautionary_change = False
direction_change = None
next_leaf = abjad.get.leaf(leaf, 1)
this_contact_point = bow_contact_point
if this_contact_point is None:
return
next_contact_point = abjad.get.indicator(next_leaf, BowAnglePoint)
if next_contact_point is None:
return
previous_leaf = abjad.get.leaf(leaf, -1)
previous_contact_point = None
if previous_leaf is not None:
previous_contact_points = previous_leaf._get_indicators(BowAnglePoint)
if previous_contact_points:
previous_contact_point = previous_contact_points[0]
if (
leaf is leaves[0]
or previous_contact_point is None
or previous_contact_point.degrees is None
):
if this_contact_point < next_contact_point:
direction_change = abjad.Down
elif next_contact_point < this_contact_point:
direction_change = abjad.Up
else:
previous_leaf = abjad.get.leaf(leaf, -1)
previous_contact_point = abjad.get.indicator(previous_leaf, BowAnglePoint)
if (
previous_contact_point < this_contact_point
and next_contact_point < this_contact_point
):
direction_change = abjad.Up
elif (
this_contact_point < previous_contact_point
and this_contact_point < next_contact_point
):
direction_change = abjad.Down
elif this_contact_point == previous_contact_point:
if this_contact_point < next_contact_point:
cautionary_change = True
direction_change = abjad.Down
elif next_contact_point < this_contact_point:
cautionary_change = True
direction_change = abjad.Up
if direction_change is None:
return
if direction_change == abjad.Up:
string = r"\evans-counterclockwise-arc \translate #'(0.2 . 0.75) \scale #'(0.7 . 0.7)"
else:
string = (
r"\evans-clockwise-arc \translate #'(0.2 . 0.75) \scale #'(0.7 . 0.7)"
)
if cautionary_change:
string = rf"\parenthesize {string}"
string = "^ " + string
literal = abjad.LilyPondLiteral(string, "after")
abjad.attach(literal, leaf)
def _next_leaf_is_bowed(leaf, leaves):
if leaf is leaves[-1]:
return False
silent_prototype = (abjad.MultimeasureRest, abjad.Rest, abjad.Skip)
next_leaf = abjad.get.leaf(leaf, 1)
if next_leaf is None or isinstance(next_leaf, silent_prototype):
return False
next_contact_point = abjad.get.indicator(next_leaf, BowAnglePoint)
if next_contact_point is None:
return False
elif next_contact_point.degrees is None:
return False
return True
def _format_leaf(leaf, leaves):
indicators = abjad.get.indicators(leaf)
bow_contact_point = indicators[0]
bow_motion_technique = indicators[1]
if bow_contact_point is None:
return
if bow_contact_point.degrees is None:
abjad.tweak(leaf.note_head).style = "#'cross"
return
if len(leaves) == 1:
return
_make_bow_contact_point_tweaks(leaf, bow_contact_point)
if not _next_leaf_is_bowed(leaf, leaves):
return
glissando = abjad.Glissando()
if bow_motion_technique is not None:
style = f"#'{bow_motion_technique.glissando_style}"
abjad.tweak(glissando).style = style
abjad.attach(glissando, leaf, tag=tag)
if not omit_bow_changes:
_make_bow_change_contributions(leaf, leaves, bow_contact_point)
leaves = abjad.Selection(argument).leaves()
assert isinstance(leaves, abjad.Selection), repr(leaves)
for leaf in leaves:
_format_leaf(leaf, leaves)