"""
Indicators.
"""
import collections
import dataclasses
import fractions
import functools
import math
import typing
from . import contributions as _contributions
from . import duration as _duration
from . import enums as _enums
from . import math as _math
from . import overrides as _overrides
from . import pitch as _pitch
from . import sequence as _sequence
from . import string as _string
_EMPTY_CHORD = "<>"
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Arpeggio:
r"""
Arpeggio.
.. container:: example
Without direction arrow:
>>> chord = abjad.Chord("<c' e' g' c''>4")
>>> arpeggio = abjad.Arpeggio()
>>> abjad.attach(arpeggio, chord)
>>> staff = abjad.Staff([chord])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
<c' e' g' c''>4
\arpeggio
}
.. container:: example
With direction arrow:
>>> chord = abjad.Chord("<c' e' g' c''>4")
>>> arpeggio = abjad.Arpeggio(direction=abjad.DOWN)
>>> abjad.attach(arpeggio, chord)
>>> staff = abjad.Staff([chord])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\arpeggioArrowDown
<c' e' g' c''>4
\arpeggio
}
.. container:: example
Tweaks:
>>> chord = abjad.Chord("<c' e' g' c''>4")
>>> arpeggio = abjad.Arpeggio()
>>> bundle = abjad.bundle(arpeggio, r"- \tweak color #blue")
>>> abjad.attach(bundle, chord)
>>> staff = abjad.Staff([chord])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
<c' e' g' c''>4
- \tweak color #blue
\arpeggio
}
"""
direction: _enums.Vertical | None = None
post_event: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
if self.direction is not None:
assert isinstance(self.direction, _enums.Vertical), repr(self.direction)
def _get_lilypond_format(self):
return r"\arpeggio"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.after.articulations.append(r"\arpeggio")
if self.direction in (_enums.UP, _enums.DOWN):
if self.direction is _enums.UP:
command = r"\arpeggioArrowUp"
else:
command = r"\arpeggioArrowDown"
contributions.before.commands.append(command)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Articulation:
r"""
Articulation.
.. container:: example
>>> abjad.Articulation("staccato")
Articulation(name='staccato')
>>> abjad.Articulation(".")
Articulation(name='.')
Use ``direction=abjad.UP`` like this:
>>> note = abjad.Note("c'4")
>>> articulation = abjad.Articulation("staccato")
>>> abjad.attach(articulation, note, direction=abjad.UP)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
^ \staccato
Use ``direction=abjad.DOWN`` like this:
>>> note = abjad.Note("c'4")
>>> articulation = abjad.Articulation("staccato")
>>> abjad.attach(articulation, note, direction=abjad.DOWN)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
_ \staccato
Use ``dataclasses.replace()`` like this:
>>> import dataclasses
>>> articulation = abjad.Articulation(".")
>>> dataclasses.replace(articulation)
Articulation(name='.')
"""
name: str
post_event: typing.ClassVar[bool] = True
shortcut_to_word: typing.ClassVar[dict[str, str]] = {
"^": "marcato",
"+": "stopped",
"-": "tenuto",
"|": "staccatissimo",
">": "accent",
".": "staccato",
"_": "portato",
}
[docs] def __post_init__(self):
assert isinstance(self.name, str), repr(self.name)
def _get_lilypond_format(self, wrapper=None):
if self.name:
string = self.shortcut_to_word.get(self.name)
if not string:
string = self.name
if wrapper is not None and wrapper.direction:
direction_ = _string.to_tridirectional_lilypond_symbol(
wrapper.direction
)
direction = direction_
else:
direction = "-"
return rf"{direction} \{string}"
else:
return ""
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format(wrapper=wrapper)
contributions.after.articulations.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class BarLine:
r"""
Bar line.
.. container:: example
Final bar line:
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4", name="Staff")
>>> score = abjad.Score([staff], name="Score")
>>> bar_line = abjad.BarLine("|.")
>>> abjad.attach(bar_line, staff[-1])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\context Score = "Score"
<<
\context Staff = "Staff"
{
c'4
d'4
e'4
f'4
\bar "|."
}
>>
.. container:: example
Specify repeat bars like this:
>>> staff = abjad.Staff("c'4 d' e' f' g' a' b' c''", name="Staff")
>>> score = abjad.Score([staff], name="Score")
>>> abjad.attach(abjad.BarLine(".|:"), staff[3])
>>> abjad.attach(abjad.BarLine(":|."), staff[-1])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\context Score = "Score"
<<
\context Staff = "Staff"
{
c'4
d'4
e'4
f'4
\bar ".|:"
g'4
a'4
b'4
c''4
\bar ":|."
}
>>
This allows you to bypass LilyPond's ``\volta`` command.
"""
abbreviation: str = "|"
site: str = "after"
context: typing.ClassVar[str] = "Score"
# find_context_on_attach: typing.ClassVar[bool] = True
known_abbreviations: typing.ClassVar[tuple[str, ...]] = (
"",
"|",
".",
"||",
".|",
"..",
"|.|",
"|.",
";",
"!",
".|:",
":..:",
":|.|:",
":|.:",
":.|.:",
"[|:",
":|][|:",
":|]",
":|.",
"'",
)
[docs] def __post_init__(self):
"""
LilyPond fails to error on unknown bar-line abbreviation, so we check here.
"""
if self.abbreviation not in self.known_abbreviations:
raise Exception(f"unknown bar-line abbreviation: {self.abbreviation!r}.")
def _get_lilypond_format(self):
return rf'\bar "{self.abbreviation}"'
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
site = getattr(contributions, self.site)
site.commands.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class BeamCount:
r"""
LilyPond ``\setLeftBeamCount``, ``\setRightBeamCount`` command.
.. container:: example
>>> abjad.BeamCount()
BeamCount(left=0, right=0)
"""
left: int = 0
right: int = 0
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = rf"\set stemLeftBeamCount = {self.left}"
contributions.before.commands.append(string)
string = rf"\set stemRightBeamCount = {self.right}"
contributions.before.commands.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class BendAfter:
r"""
Fall or doit.
.. container:: example
A fall:
>>> note = abjad.Note("c'4")
>>> bend = abjad.BendAfter(-4)
>>> abjad.attach(bend, note)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
- \bendAfter #'-4
.. container:: example
A doit:
>>> note = abjad.Note("c'4")
>>> bend = abjad.BendAfter(2)
>>> abjad.attach(bend, note)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
- \bendAfter #'2
.. container:: example
Tweaks:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> bend_after = abjad.BendAfter(-4)
>>> bundle = abjad.bundle(bend_after, r"- \tweak color #blue")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #blue
- \bendAfter #'-4
d'4
e'4
f'4
}
"""
bend_amount: int | float = -4
post_event: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "after"
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
def _get_lilypond_format(self):
return rf"- \bendAfter #'{self.bend_amount}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format()
contributions.after.articulations.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class BreathMark:
r"""
Breath mark.
.. container:: example
>>> voice = abjad.Voice("c'8 d' e' f' g' a' b' c''")
>>> staff = abjad.Staff([voice])
>>> abjad.beam(voice[:4])
>>> abjad.beam(voice[4:])
>>> abjad.attach(abjad.BreathMark(), voice[3])
>>> abjad.attach(abjad.BreathMark(), voice[7])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\new Voice
{
c'8
[
d'8
e'8
f'8
]
\breathe
g'8
[
a'8
b'8
c''8
]
\breathe
}
}
.. container:: example
REGRESSION. Abjad parses LilyPond's ``\breathe`` command correctly:
>>> staff = abjad.Staff(r"c'4 d' e' f' \breathe")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
d'4
e'4
f'4
\breathe
}
.. container:: example
Tweaks:
>>> note = abjad.Note("c'4")
>>> breath = abjad.BreathMark()
>>> bundle = abjad.bundle(breath, r"\tweak color #blue")
>>> abjad.attach(bundle, note)
>>> staff = abjad.Staff([note])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
\tweak color #blue
\breathe
}
"""
site: typing.ClassVar[str] = "after"
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
def _before_attach(self, deactivate, component):
for indicator in component._get_indicators():
if isinstance(indicator, type(self)) and indicator.site == self.site:
classname = type(component).__name__
message = f"can not attach {self!r} to {classname}:"
message += f"\n {indicator!r} is already attached to {classname}."
raise Exception(message)
def _get_lilypond_format(self):
return r"\breathe"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format()
contributions.after.commands.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Clef:
r"""
Clef.
.. container:: example
Some available clefs:
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8 g'8 a'8 b'8 c''8")
>>> clef = abjad.Clef("treble")
>>> abjad.attach(clef, staff[0])
>>> clef = abjad.Clef("alto")
>>> abjad.attach(clef, staff[1])
>>> clef = abjad.Clef("bass")
>>> abjad.attach(clef, staff[2])
>>> clef = abjad.Clef("treble^8")
>>> abjad.attach(clef, staff[3])
>>> clef = abjad.Clef("bass_8")
>>> abjad.attach(clef, staff[4])
>>> clef = abjad.Clef("tenor")
>>> abjad.attach(clef, staff[5])
>>> clef = abjad.Clef("bass^15")
>>> abjad.attach(clef, staff[6])
>>> clef = abjad.Clef("percussion")
>>> abjad.attach(clef, staff[7])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\clef "treble"
c'8
\clef "alto"
d'8
\clef "bass"
e'8
\clef "treble^8"
f'8
\clef "bass_8"
g'8
\clef "tenor"
a'8
\clef "bass^15"
b'8
\clef "percussion"
c''8
}
.. container:: example
Clefs can be tagged:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> abjad.attach(
... abjad.Clef("treble"), staff[0], tag=abjad.Tag("+PARTS")
... )
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff, tags=True)
>>> print(string)
\new Staff
{
%! +PARTS
\clef "treble"
c'4
d'4
e'4
f'4
}
.. container:: example
LilyPond can not handle simultaneous clefs:
>>> voice_1 = abjad.Voice("e'8 g' f' a' g' b'")
>>> voice_2 = abjad.Voice("c'4. c,8 b,, a,,")
>>> staff = abjad.Staff([voice_1, voice_2], simultaneous=True)
>>> abjad.attach(abjad.Clef("treble"), voice_1[0], context="Voice")
>>> command = abjad.VoiceNumber(1)
>>> abjad.attach(command, voice_1[0])
>>> voice_1.consists_commands.append("Clef_engraver")
>>> abjad.attach(abjad.Clef("treble"), voice_2[0], context="Voice")
>>> abjad.attach(abjad.Clef("bass"), voice_2[1], context="Voice")
>>> command = abjad.VoiceNumber(2)
>>> abjad.attach(command, voice_2[0])
>>> voice_2.consists_commands.append("Clef_engraver")
>>> staff.remove_commands.append("Clef_engraver")
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\remove Clef_engraver
}
<<
\new Voice
\with
{
\consists Clef_engraver
}
{
\voiceOne
\clef "treble"
e'8
g'8
f'8
a'8
g'8
b'8
}
\new Voice
\with
{
\consists Clef_engraver
}
{
\voiceTwo
\clef "treble"
c'4.
\clef "bass"
c,8
b,,8
a,,8
}
>>
But Abjad components work fine:
>>> for leaf in abjad.select.leaves(voice_1):
... leaf, abjad.get.effective(leaf, abjad.Clef)
...
(Note("e'8"), Clef(name='treble', hide=False))
(Note("g'8"), Clef(name='treble', hide=False))
(Note("f'8"), Clef(name='treble', hide=False))
(Note("a'8"), Clef(name='treble', hide=False))
(Note("g'8"), Clef(name='treble', hide=False))
(Note("b'8"), Clef(name='treble', hide=False))
>>> for leaf in abjad.select.leaves(voice_2):
... leaf, abjad.get.effective(leaf, abjad.Clef)
...
(Note("c'4."), Clef(name='treble', hide=False))
(Note('c,8'), Clef(name='bass', hide=False))
(Note('b,,8'), Clef(name='bass', hide=False))
(Note('a,,8'), Clef(name='bass', hide=False))
.. container:: example
Middle-C position:
>>> abjad.Clef("treble").middle_c_position()
StaffPosition(number=-6)
>>> abjad.Clef("alto").middle_c_position()
StaffPosition(number=0)
.. container:: example
Set ``hide=True`` when clef should not appear in output (but should still
determine effective clef).
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> abjad.attach(abjad.Clef("treble"), staff[0])
>>> abjad.attach(abjad.Clef("alto", hide=True), staff[2])
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\clef "treble"
c'4
d'4
e'4
f'4
}
>>> for leaf in abjad.iterate.leaves(staff):
... leaf, abjad.get.effective(leaf, abjad.Clef)
...
(Note("c'4"), Clef(name='treble', hide=False))
(Note("d'4"), Clef(name='treble', hide=False))
(Note("e'4"), Clef(name='alto', hide=True))
(Note("f'4"), Clef(name='alto', hide=True))
"""
name: str = "treble"
hide: bool = dataclasses.field(compare=False, default=False)
context: typing.ClassVar[str] = "Staff"
# find_context_on_attach: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
redraw: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "before"
clef_name_to_middle_c_position = {
"treble": -6,
"alto": 0,
"varC": 0,
"tenor": 2,
"tenorvarC": 2,
"bass": 6,
"french": -8,
"soprano": -4,
"mezzosoprano": -2,
"baritone": 4,
"varbaritone": 4,
"percussion": 0,
"tab": 0,
}
_to_width = {
"alto": 2.75,
"varC": 2.75,
"bass": 2.75,
"percussion": 2.5,
"tenor": 2.75,
"tenorvarC": 2.75,
"treble": 2.5,
}
[docs] def __post_init__(self):
assert isinstance(self.name, str), repr(self.name)
assert isinstance(self.hide, bool), repr(self.hide)
def _calculate_middle_c_position(self, clef_name):
alteration = 0
if "_" in self.name:
base_name, part, suffix = clef_name.partition("_")
if suffix == "8":
alteration = 7
elif suffix == "15":
alteration = 13
else:
raise Exception(f"bad clef alteration suffix: {suffix!r}.")
elif "^" in self.name:
base_name, part, suffix = clef_name.partition("^")
if suffix == "8":
alteration = -7
elif suffix == "15":
alteration = -13
else:
raise Exception(f"bad clef alteration suffix: {suffix!r}.")
else:
base_name = clef_name
return self.clef_name_to_middle_c_position[base_name] + alteration
def _clef_name_to_staff_position_zero(self, clef_name):
return {
"treble": _pitch.NamedPitch("B4"),
"alto": _pitch.NamedPitch("C4"),
"varC": _pitch.NamedPitch("C4"),
"tenor": _pitch.NamedPitch("A3"),
"tenorvarC": _pitch.NamedPitch("A3"),
"bass": _pitch.NamedPitch("D3"),
"french": _pitch.NamedPitch("D5"),
"soprano": _pitch.NamedPitch("G4"),
"mezzosoprano": _pitch.NamedPitch("E4"),
"baritone": _pitch.NamedPitch("F3"),
"varbaritone": _pitch.NamedPitch("F3"),
"percussion": None,
"tab": None,
}[clef_name]
def _get_lilypond_format(self):
return rf'\clef "{self.name}"'
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
if not self.hide:
contributions.opening.commands.append(self._get_lilypond_format())
return contributions
[docs] @classmethod
def from_pitches(class_, pitches) -> "Clef":
"""
Makes clef from ``pitches``.
.. container:: example
>>> notes = abjad.makers.make_notes(list(range(-12, -6)), [(1, 4)])
>>> staff = abjad.Staff(notes)
>>> pitches = abjad.iterate.pitches(staff)
>>> abjad.Clef.from_pitches(pitches)
Clef(name='bass', hide=False)
Chooses between treble and bass based on minimal number of ledger lines.
"""
diatonic_pitch_numbers = [
pitch._get_diatonic_pitch_number() for pitch in pitches
]
max_diatonic_pitch_number = max(diatonic_pitch_numbers)
min_diatonic_pitch_number = min(diatonic_pitch_numbers)
lowest_treble_line_pitch = _pitch.NamedPitch("E4")
lowest_treble_line_diatonic_pitch_number = (
lowest_treble_line_pitch._get_diatonic_pitch_number()
)
candidate_steps_below_treble = (
lowest_treble_line_diatonic_pitch_number - min_diatonic_pitch_number
)
highest_bass_line_pitch = _pitch.NamedPitch("A3")
highest_bass_line_diatonic_pitch_number = (
highest_bass_line_pitch._get_diatonic_pitch_number()
)
candidate_steps_above_bass = (
max_diatonic_pitch_number - highest_bass_line_diatonic_pitch_number
)
if candidate_steps_above_bass < candidate_steps_below_treble:
return class_("bass")
else:
return class_("treble")
[docs] def middle_c_position(self):
"""
Gets middle C position.
"""
middle_c_position = self._calculate_middle_c_position(self.name)
middle_c_position = _pitch.StaffPosition(middle_c_position)
return middle_c_position
[docs] def to_pitch(self, staff_position) -> _pitch.NamedPitch:
"""
Changes ``staff_position`` to pitch.
Treble clef:
.. container:: example
>>> clef = abjad.Clef("treble")
>>> for n in range(-6, 6):
... staff_position = abjad.StaffPosition(n)
... pitch = clef.to_pitch(staff_position)
... print(f"{staff_position!r:25}{pitch!r}")
...
StaffPosition(number=-6) NamedPitch("c'")
StaffPosition(number=-5) NamedPitch("d'")
StaffPosition(number=-4) NamedPitch("e'")
StaffPosition(number=-3) NamedPitch("f'")
StaffPosition(number=-2) NamedPitch("g'")
StaffPosition(number=-1) NamedPitch("a'")
StaffPosition(number=0) NamedPitch("b'")
StaffPosition(number=1) NamedPitch("c''")
StaffPosition(number=2) NamedPitch("d''")
StaffPosition(number=3) NamedPitch("e''")
StaffPosition(number=4) NamedPitch("f''")
StaffPosition(number=5) NamedPitch("g''")
.. container:: example
Bass clef:
>>> clef = abjad.Clef("bass")
>>> for n in range(-6, 6):
... staff_position = abjad.StaffPosition(n)
... pitch = clef.to_pitch(staff_position)
... print(f"{staff_position!r:25}{pitch!r}")
...
StaffPosition(number=-6) NamedPitch('e,')
StaffPosition(number=-5) NamedPitch('f,')
StaffPosition(number=-4) NamedPitch('g,')
StaffPosition(number=-3) NamedPitch('a,')
StaffPosition(number=-2) NamedPitch('b,')
StaffPosition(number=-1) NamedPitch('c')
StaffPosition(number=0) NamedPitch('d')
StaffPosition(number=1) NamedPitch('e')
StaffPosition(number=2) NamedPitch('f')
StaffPosition(number=3) NamedPitch('g')
StaffPosition(number=4) NamedPitch('a')
StaffPosition(number=5) NamedPitch('b')
.. container:: example
Alto clef:
>>> clef = abjad.Clef("alto")
>>> for n in range(-6, 6):
... staff_position = abjad.StaffPosition(n)
... pitch = clef.to_pitch(staff_position)
... print(f"{staff_position!r:25}{pitch!r}")
...
StaffPosition(number=-6) NamedPitch('d')
StaffPosition(number=-5) NamedPitch('e')
StaffPosition(number=-4) NamedPitch('f')
StaffPosition(number=-3) NamedPitch('g')
StaffPosition(number=-2) NamedPitch('a')
StaffPosition(number=-1) NamedPitch('b')
StaffPosition(number=0) NamedPitch("c'")
StaffPosition(number=1) NamedPitch("d'")
StaffPosition(number=2) NamedPitch("e'")
StaffPosition(number=3) NamedPitch("f'")
StaffPosition(number=4) NamedPitch("g'")
StaffPosition(number=5) NamedPitch("a'")
.. container:: example
Percussion clef:
>>> clef = abjad.Clef("percussion")
>>> for n in range(-6, 6):
... staff_position = abjad.StaffPosition(n)
... pitch = clef.to_pitch(staff_position)
... print(f"{staff_position!r:25}{pitch!r}")
...
StaffPosition(number=-6) NamedPitch('d')
StaffPosition(number=-5) NamedPitch('e')
StaffPosition(number=-4) NamedPitch('f')
StaffPosition(number=-3) NamedPitch('g')
StaffPosition(number=-2) NamedPitch('a')
StaffPosition(number=-1) NamedPitch('b')
StaffPosition(number=0) NamedPitch("c'")
StaffPosition(number=1) NamedPitch("d'")
StaffPosition(number=2) NamedPitch("e'")
StaffPosition(number=3) NamedPitch("f'")
StaffPosition(number=4) NamedPitch("g'")
StaffPosition(number=5) NamedPitch("a'")
"""
assert isinstance(staff_position, _pitch.StaffPosition), repr(staff_position)
offset_staff_position_number = staff_position.number
offset_staff_position_number -= self.middle_c_position().number
offset_staff_position = _pitch.StaffPosition(offset_staff_position_number)
octave_number = offset_staff_position.number // 7 + 4
diatonic_pc_number = offset_staff_position.number % 7
pitch_class_number = _pitch._diatonic_pc_number_to_pitch_class_number[
diatonic_pc_number
]
pitch_number = 12 * (octave_number - 4)
pitch_number += pitch_class_number
named_pitch = _pitch.NamedPitch(pitch_number)
return named_pitch
[docs] def to_staff_position(self, pitch) -> _pitch.StaffPosition:
r"""
Changes ``pitch`` to staff position.
Changes C#5 to absolute staff position:
.. container:: example
>>> pitch = abjad.NamedPitch("C#5")
>>> abjad.Clef("alto").to_staff_position(pitch)
StaffPosition(number=7)
>>> abjad.Clef("treble").to_staff_position(pitch)
StaffPosition(number=1)
>>> abjad.Clef("bass").to_staff_position(pitch)
StaffPosition(number=13)
.. container:: example
Labels absolute staff position:
>>> string = "g16 a b c' d' e' f' g' a' b' c'' d'' e'' f'' g'' a''"
>>> staff = abjad.Staff(string)
>>> clef = abjad.Clef("alto")
>>> for note in staff:
... staff_position = clef.to_staff_position(note.written_pitch)
... markup = abjad.Markup(rf"\markup {staff_position.number}")
... abjad.attach(markup, note)
...
>>> abjad.override(staff).TextScript.staff_padding = 5
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override TextScript.staff-padding = 5
}
{
g16
- \markup -3
a16
- \markup -2
b16
- \markup -1
c'16
- \markup 0
d'16
- \markup 1
e'16
- \markup 2
f'16
- \markup 3
g'16
- \markup 4
a'16
- \markup 5
b'16
- \markup 6
c''16
- \markup 7
d''16
- \markup 8
e''16
- \markup 9
f''16
- \markup 10
g''16
- \markup 11
a''16
- \markup 12
}
.. container:: example
Labels staff position in bass clef:
>>> string = "g,16 a, b, c d e f g a b c' d' e' f' g' a'"
>>> staff = abjad.Staff(string)
>>> clef = abjad.Clef("bass")
>>> for note in staff:
... staff_position = clef.to_staff_position(note.written_pitch)
... markup = abjad.Markup(rf"\markup {staff_position.number}")
... abjad.attach(markup, note)
...
>>> abjad.attach(abjad.Clef("bass"), staff[0])
>>> abjad.override(staff).TextScript.staff_padding = 5
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override TextScript.staff-padding = 5
}
{
\clef "bass"
g,16
- \markup -4
a,16
- \markup -3
b,16
- \markup -2
c16
- \markup -1
d16
- \markup 0
e16
- \markup 1
f16
- \markup 2
g16
- \markup 3
a16
- \markup 4
b16
- \markup 5
c'16
- \markup 6
d'16
- \markup 7
e'16
- \markup 8
f'16
- \markup 9
g'16
- \markup 10
a'16
- \markup 11
}
"""
assert isinstance(pitch, _pitch.NamedPitch)
staff_position_number = pitch._get_diatonic_pitch_number()
staff_position_number += self.middle_c_position().number
staff_position = _pitch.StaffPosition(staff_position_number)
return staff_position
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class ColorFingering:
r"""
Color fingering.
.. container:: example
>>> fingering = abjad.ColorFingering(1)
>>> note = abjad.Note("c'4")
>>> abjad.attach(fingering, note, direction=abjad.UP)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
^ \markup { \override #'(circle-padding . 0.25) \circle \finger 1 }
.. container:: example
Tweaks:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> fingering = abjad.ColorFingering(1)
>>> bundle = abjad.bundle(fingering, r"- \tweak color #blue")
>>> abjad.attach(bundle, staff[0], direction=abjad.UP)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #blue
^ \markup { \override #'(circle-padding . 0.25) \circle \finger 1 }
d'4
e'4
f'4
}
Color fingerings indicate alternate woodwind fingerings by amount of pitch of timbre
deviation.
"""
number: int
directed: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "after"
[docs] def __post_init__(self):
assert isinstance(self.number, int), repr(self.number)
def _get_lilypond_format(self):
return self.markup._get_lilypond_format()
def _get_contributions(self, *, component=None, wrapper=None):
return self.markup._get_contributions(component=component, wrapper=wrapper)
@property
def markup(self) -> typing.Optional["Markup"]:
r"""
Gets markup of color fingering.
.. container:: example
First color fingering:
>>> fingering = abjad.ColorFingering(1)
>>> string = abjad.lilypond(fingering.markup)
>>> print(string)
\markup { \override #'(circle-padding . 0.25) \circle \finger 1 }
"""
if self.number is None:
return None
string = rf"\override #'(circle-padding . 0.25) \circle \finger {self.number}"
string = rf"\markup {{ {string} }}"
markup = Markup(string)
return markup
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Fermata:
r"""
Fermata.
.. container:: example
A short fermata:
>>> score = abjad.Score([abjad.Staff([abjad.Note("c'4")])])
>>> fermata = abjad.Fermata(command='shortfermata')
>>> abjad.attach(fermata, score[0][0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
c'4
\shortfermata
}
>>
.. container:: example
A fermata:
>>> score = abjad.Score([abjad.Staff([abjad.Note("c'4")])])
>>> fermata = abjad.Fermata()
>>> abjad.attach(fermata, score[0][0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
c'4
\fermata
}
>>
.. container:: example
A long fermata:
>>> score = abjad.Score([abjad.Staff([abjad.Note("c'4")])])
>>> fermata = abjad.Fermata('longfermata')
>>> abjad.attach(fermata, score[0][0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
c'4
\longfermata
}
>>
.. container:: example
A very long fermata:
>>> score = abjad.Score([abjad.Staff([abjad.Note("c'4")])])
>>> fermata = abjad.Fermata('verylongfermata')
>>> abjad.attach(fermata, score[0][0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
c'4
\verylongfermata
}
>>
.. container:: example
Tweaks:
>>> note = abjad.Note("c'4")
>>> staff = abjad.Staff([note], name="Staff")
>>> score = abjad.Score([staff], name="Score")
>>> fermata = abjad.Fermata()
>>> bundle = abjad.bundle(fermata, r"- \tweak color #blue")
>>> abjad.attach(bundle, note)
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
- \tweak color #blue
\fermata
"""
command: str = "fermata"
allowable_commands: typing.ClassVar = (
"fermata",
"longfermata",
"shortfermata",
"verylongfermata",
)
context: typing.ClassVar[str] = "Score"
# find_context_on_attach: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "after"
[docs] def __post_init__(self):
assert self.command in self.allowable_commands, repr(self.command)
def _get_lilypond_format(self):
return rf"\{self.command}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.after.articulations.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Glissando:
r"""
LilyPond ``\glissando`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'", name="Voice")
>>> glissando = abjad.Glissando()
>>> bundle = abjad.bundle(glissando, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\context Voice = "Voice"
{
c'4
- \tweak color #blue
\glissando
d'4
e'4
f'4
}
"""
zero_padding: bool = False
context: typing.ClassVar[str] = "Voice"
# find_context_on_attach: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.zero_padding, bool), repr(self.zero_padding)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
strings = []
if self.zero_padding:
strings.append(r"- \abjad-zero-padding-glissando")
strings.append(r"\glissando")
contributions.after.spanner_starts.extend(strings)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class InstrumentName:
r"""
LilyPond ``\instrumentName`` setting.
.. container:: example
Set instrument name to markup like this:
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
>>> markup = abjad.Markup(r"\markup Cellos")
>>> instrument_name = abjad.InstrumentName(markup)
>>> abjad.attach(instrument_name, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\set Staff.instrumentName = \markup Cellos
c'4
d'4
e'4
f'4
}
Set instrument name to custom-defined function like this:
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
>>> instrument_name = abjad.InstrumentName(r"\my_instrument_name")
>>> abjad.attach(instrument_name, staff[0])
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\set Staff.instrumentName = \my_instrument_name
c'4
d'4
e'4
f'4
}
.. container:: example
Equality testing:
>>> name_1 = abjad.InstrumentName(r"\markup Harp", context="PianoStaff")
>>> name_2 = abjad.InstrumentName(r"\markup Harp", context="PianoStaff")
>>> name_3 = abjad.InstrumentName(r"\markup Harp", context="Staff")
>>> name_1 == name_1
True
>>> name_1 == name_2
True
>>> name_1 == name_3
False
>>> name_2 == name_1
True
>>> name_2 == name_2
True
>>> name_2 == name_3
False
>>> name_3 == name_1
False
>>> name_3 == name_2
False
>>> name_3 == name_3
True
"""
markup: typing.Union[str, "Markup"]
_: dataclasses.KW_ONLY
context: str = "Staff"
# find_context_on_attach: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "before"
[docs] def __post_init__(self):
assert isinstance(self.markup, str | Markup), repr(self.markup)
assert isinstance(self.context, str), repr(self.context)
@property
def _lilypond_type(self):
if isinstance(self.context, type):
return self.context.__name__
elif isinstance(self.context, str):
return self.context
else:
return type(self.context).__name__
def _get_lilypond_format(self, context=None):
result = []
if isinstance(context, str):
pass
elif context is not None:
context = context.lilypond_type
else:
context = self._lilypond_type
if isinstance(self.markup, Markup):
string = self.markup.string
else:
assert isinstance(self.markup, str)
string = self.markup
string = rf"\set {context!s}.instrumentName = {string}"
result.append(string)
return result
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
site = getattr(contributions, self.site)
site.commands.extend(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class KeyCluster:
r"""
Key cluster.
.. container:: example
Includes flat markup:
>>> chord = abjad.Chord("<c' e' g' b' d'' f''>8")
>>> key_cluster = abjad.KeyCluster(include_flat_markup=True)
>>> abjad.attach(key_cluster, chord, direction=abjad.UP)
>>> staff = abjad.Staff([chord])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\once \override Accidental.stencil = ##f
\once \override AccidentalCautionary.stencil = ##f
\once \override Arpeggio.X-offset = #-2
\once \override NoteHead.stencil = #ly:text-interface::print
\once \override NoteHead.text =
\markup \filled-box #'(-0.6 . 0.6) #'(-0.7 . 0.7) #0.25
<c' e' g' b' d'' f''>8
^ \markup \center-align \concat { \natural \flat }
}
.. container:: example
Includes natural markup:
>>> chord = abjad.Chord("<c' e' g' b' d'' f''>8")
>>> key_cluster = abjad.KeyCluster(include_natural_markup=True)
>>> abjad.attach(key_cluster, chord, direction=abjad.UP)
>>> staff = abjad.Staff([chord])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\once \override Accidental.stencil = ##f
\once \override AccidentalCautionary.stencil = ##f
\once \override Arpeggio.X-offset = #-2
\once \override NoteHead.stencil = #ly:text-interface::print
\once \override NoteHead.text =
\markup \filled-box #'(-0.6 . 0.6) #'(-0.7 . 0.7) #0.25
<c' e' g' b' d'' f''>8
^ \markup \center-align \concat { \natural \flat }
}
.. container:: example
Key cluster output includes overrides instead of tweaks:
>>> chord = abjad.Chord("<c' e' g' b' d'' f''>8")
>>> key_cluster = abjad.KeyCluster()
>>> abjad.attach(key_cluster, chord, direction=abjad.UP)
>>> string = abjad.lilypond(chord)
>>> print(string)
\once \override Accidental.stencil = ##f
\once \override AccidentalCautionary.stencil = ##f
\once \override Arpeggio.X-offset = #-2
\once \override NoteHead.stencil = #ly:text-interface::print
\once \override NoteHead.text =
\markup \filled-box #'(-0.6 . 0.6) #'(-0.7 . 0.7) #0.25
<c' e' g' b' d'' f''>8
^ \markup \center-align \concat { \natural \flat }
The reason for this is that chords contain multiple note-heads: if key cluster
formatted tweaks instead of overrides, the five format commands shown above would
need to be duplicated immediately before each note-head.
"""
include_flat_markup: bool = True
include_natural_markup: bool = True
directed: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.include_flat_markup, bool), repr(
self.include_flat_markup
)
assert isinstance(self.include_natural_markup, bool), repr(
self.include_natural_markup
)
def _get_contributions(self, wrapper=None):
contributions = _contributions.ContributionsBySite()
contributions.grob_overrides.append(
"\\once \\override Accidental.stencil = ##f\n"
"\\once \\override AccidentalCautionary.stencil = ##f\n"
"\\once \\override Arpeggio.X-offset = #-2\n"
"\\once \\override NoteHead.stencil = #ly:text-interface::print\n"
"\\once \\override NoteHead.text =\n"
"\\markup \\filled-box #'(-0.6 . 0.6) #'(-0.7 . 0.7) #0.25"
)
if self.include_flat_markup and self.include_natural_markup:
string = r"\center-align \concat { \natural \flat }"
elif self.include_flat_markup:
string = r"\center-align \flat"
else:
string = r"\center-align \natural"
string = rf"\markup {string}"
if wrapper.direction is _enums.UP:
string = rf"^ {string}"
elif wrapper.direction is _enums.DOWN:
string = rf"_ {string}"
contributions.after.markup.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class LilyPondLiteral:
r"""
LilyPond literal.
.. container:: example
Dotted slur:
>>> voice = abjad.Voice("c'8 d'8 e'8 f'8")
>>> abjad.slur(voice[:])
>>> literal = abjad.LilyPondLiteral(r"\slurDotted", site="before")
>>> abjad.attach(literal, voice[0])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
\slurDotted
c'8
(
d'8
e'8
f'8
)
}
.. container:: example
Use the absolute before and absolute after format sites like this:
>>> voice = abjad.Voice("c'8 d'8 e'8 f'8")
>>> abjad.slur(voice[:])
>>> literal = abjad.LilyPondLiteral(r"\slurDotted", site="before")
>>> abjad.attach(literal, voice[0])
>>> literal = abjad.LilyPondLiteral("", site="absolute_before")
>>> abjad.attach(literal, voice[0])
>>> literal = abjad.LilyPondLiteral(
... "% before all formatting",
... site="absolute_before",
... )
>>> abjad.attach(literal, voice[0])
>>> literal = abjad.LilyPondLiteral("", site="absolute_after")
>>> abjad.attach(literal, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
<BLANKLINE>
% before all formatting
\slurDotted
c'8
(
d'8
e'8
f'8
)
<BLANKLINE>
}
.. container:: example
LilyPond literals can be tagged:
>>> voice = abjad.Voice("c'8 d'8 e'8 f'8")
>>> abjad.slur(voice[:])
>>> literal = abjad.LilyPondLiteral(r"\slurDotted", site="before")
>>> abjad.attach(literal, voice[0], tag=abjad.Tag("+PARTS"))
>>> abjad.show(voice) # doctest: +SKIP
>>> string = abjad.lilypond(voice, tags=True)
>>> print(string)
\new Voice
{
%! +PARTS
\slurDotted
c'8
(
d'8
e'8
f'8
)
}
.. container:: example
Multiline input is allowed:
>>> voice = abjad.Voice("c'8 d'8 e'8 f'8")
>>> abjad.slur(voice[:])
>>> lines = [
... r"\stopStaff",
... r"\startStaff",
... r"\once \override Staff.StaffSymbol.color = #red",
... ]
>>> literal = abjad.LilyPondLiteral(lines, site="before")
>>> abjad.attach(literal, voice[2], tag=abjad.Tag("+PARTS"))
>>> abjad.show(voice) # doctest: +SKIP
>>> string = abjad.lilypond(voice, tags=True)
>>> print(string)
\new Voice
{
c'8
(
d'8
%! +PARTS
\stopStaff
%! +PARTS
\startStaff
%! +PARTS
\once \override Staff.StaffSymbol.color = #red
e'8
f'8
)
}
.. container:: example
REGRESSION. Duplicate literals are allowed:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> literal = abjad.LilyPondLiteral("% text", site="before")
>>> abjad.attach(literal, staff[0])
>>> literal = abjad.LilyPondLiteral("% text", site="before")
>>> abjad.attach(literal, staff[0])
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
% text
% text
c'4
d'4
e'4
f'4
}
.. container:: example
Directed literal:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> literal = abjad.LilyPondLiteral(r"\f", directed=True, site="after")
>>> bundle = abjad.bundle(
... literal,
... r"- \tweak color #blue",
... r"- \tweak DynamicLineSpanner.staff-padding 5",
... )
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak DynamicLineSpanner.staff-padding 5
- \tweak color #blue
\f
d'4
e'4
f'4
}
.. container:: example
Nondirected literal:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> literal = abjad.LilyPondLiteral(
... r"\breathe",
... directed=False,
... site="after",
... )
>>> bundle = abjad.bundle(literal, r"\tweak color #blue")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
\tweak color #blue
\breathe
d'4
e'4
f'4
}
Proper use of the ``directed`` property entails searching the LilyPond
docs to understand whether LilyPond treats any particular command as
directed or not. Most LilyPond commands are directed. LilyPond insists
that a few commands (include ``\breathe``, ``\key``, ``\mark``) must
not be directed.
.. container:: example
Tweaks:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> literal = abjad.LilyPondLiteral(r"\f", directed=True, site="after")
>>> bundle = abjad.bundle(literal, r"- \tweak color #blue")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #blue
\f
d'4
e'4
f'4
}
"""
argument: str | list[str] = ""
_: dataclasses.KW_ONLY
site: str = "before"
directed: bool = False
allowable_sites: typing.ClassVar[tuple[str, ...]] = (
"absolute_after",
"absolute_before",
"after",
"before",
"closing",
"opening",
)
can_attach_to_containers: typing.ClassVar[bool] = True
format_leaf_children: typing.ClassVar[bool] = False
[docs] def __post_init__(self):
if not isinstance(self.argument, str):
assert all(isinstance(_, str) for _ in self.argument), repr(self.argument)
assert isinstance(self.directed, bool), repr(self.directed)
assert isinstance(self.site, str), repr(self.site)
assert self.site in self.allowable_sites, repr(self.site)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
site = getattr(contributions, self.site)
if isinstance(self.argument, str):
pieces = [self.argument]
else:
assert isinstance(self.argument, list)
pieces = self.argument[:]
site.commands.extend(pieces)
return contributions
@property
def post_event(self):
"""
Is true when literal is directed.
"""
return self.directed
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Mode:
"""
Mode.
.. container:: example
>>> abjad.Mode("major")
Mode(name='major')
"""
name: str = "major"
[docs] def __post_init__(self):
assert isinstance(self.name, str), repr(self.name)
[docs] def intervals(self):
"""
Gets intervals in mode.
.. container:: example
>>> for _ in abjad.Mode("major").intervals(): _
NamedInterval('+M2')
NamedInterval('+M2')
NamedInterval('+m2')
NamedInterval('+M2')
NamedInterval('+M2')
NamedInterval('+M2')
NamedInterval('+m2')
>>> for _ in abjad.Mode("dorian").intervals(): _
NamedInterval('+M2')
NamedInterval('+m2')
NamedInterval('+M2')
NamedInterval('+M2')
NamedInterval('+M2')
NamedInterval('+m2')
NamedInterval('+M2')
"""
intervals = []
m2 = _pitch.NamedInterval("m2")
M2 = _pitch.NamedInterval("M2")
A2 = _pitch.NamedInterval("aug2")
dorian = [M2, m2, M2, M2, M2, m2, M2]
if self.name == "dorian":
intervals.extend(_sequence.rotate(dorian, n=0))
elif self.name == "phrygian":
intervals.extend(_sequence.rotate(dorian, n=-1))
elif self.name == "lydian":
intervals.extend(_sequence.rotate(dorian, n=-2))
elif self.name == "mixolydian":
intervals.extend(_sequence.rotate(dorian, n=-3))
elif self.name in ("aeolian", "minor", "natural minor"):
intervals.extend(_sequence.rotate(dorian, n=-4))
elif self.name == "locrian":
intervals.extend(_sequence.rotate(dorian, n=-5))
elif self.name in ("ionian", "major"):
intervals.extend(_sequence.rotate(dorian, n=-6))
elif self.name == "melodic minor":
intervals.extend([M2, m2, M2, M2, M2, M2, m2])
elif self.name == "harmonic minor":
intervals.extend([M2, m2, M2, M2, m2, A2, m2])
else:
raise ValueError(f"unknown mode name: {self.name!r}.")
return intervals
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class KeySignature:
r"""
Key signature.
.. container:: example
>>> staff = abjad.Staff("e'8 fs'8 gs'8 a'8")
>>> key_signature = abjad.KeySignature(
... abjad.NamedPitchClass("e"), abjad.Mode("major")
... )
>>> abjad.attach(key_signature, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\key e \major
e'8
fs'8
gs'8
a'8
}
.. container:: example
>>> staff = abjad.Staff("e'8 fs'8 g'8 a'8")
>>> key_signature = abjad.KeySignature(
... abjad.NamedPitchClass("e"), abjad.Mode("minor")
... )
>>> abjad.attach(key_signature, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\key e \minor
e'8
fs'8
g'8
a'8
}
.. container:: example
Tweaks:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> key_signature = abjad.KeySignature(
... abjad.NamedPitchClass("e"), abjad.Mode("minor")
... )
>>> bundle = abjad.bundle(key_signature, r"\tweak color #blue")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\tweak color #blue
\key e \minor
c'4
d'4
e'4
f'4
}
"""
tonic: _pitch.NamedPitchClass = _pitch.NamedPitchClass("c")
mode: Mode = Mode("major")
context: typing.ClassVar[str] = "Staff"
# find_context_on_attach: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
redraw: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.tonic, _pitch.NamedPitchClass), repr(self.tonic)
assert isinstance(self.mode, Mode), repr(self.mod)
def _get_lilypond_format(self):
assert isinstance(self.mode, Mode), repr(self.mode)
return rf"\key {self.tonic.name} \{self.mode.name}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format()
contributions.before.commands.append(string)
return contributions
@property
def name(self) -> str:
"""
Gets name of key signature.
.. container:: example
>>> abjad.KeySignature(abjad.NamedPitchClass("e"), abjad.Mode("major")).name
'E major'
>>> abjad.KeySignature(abjad.NamedPitchClass("e"), abjad.Mode("minor")).name
'e minor'
"""
assert isinstance(self.mode, Mode)
if self.mode.name == "major":
tonic = self.tonic.name.upper()
else:
tonic = self.tonic.name
return f"{tonic!s} {self.mode.name}"
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class LaissezVibrer:
r"""
Laissez vibrer.
.. container:: example
>>> chord = abjad.Chord("<c' e' g' c''>4")
>>> laissez_vibrer = abjad.LaissezVibrer()
>>> abjad.attach(laissez_vibrer, chord)
>>> abjad.show(chord) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(chord)
>>> print(string)
<c' e' g' c''>4
\laissezVibrer
.. container:: example
Tweaks:
>>> note = abjad.Note("c'4")
>>> lv = abjad.LaissezVibrer()
>>> bundle = abjad.bundle(lv, r"- \tweak color #blue")
>>> abjad.attach(bundle, note)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
- \tweak color #blue
\laissezVibrer
"""
post_event: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "after"
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
def _get_lilypond_format(self):
return r"\laissezVibrer"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.after.articulations.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Markup:
r"""
LilyPond markup.
.. container:: example
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> markup = abjad.Markup(r'\markup \italic "Allegro assai"')
>>> abjad.attach(markup, staff[0], direction=abjad.UP)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'8
^ \markup \italic "Allegro assai"
d'8
e'8
f'8
}
.. container:: example
Tweaks:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> markup = abjad.Markup(r'\markup \bold "Allegro assai"')
>>> bundle = abjad.bundle(markup, r"- \tweak color #blue")
>>> abjad.attach(bundle, staff[0], direction=abjad.UP)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #blue
^ \markup \bold "Allegro assai"
d'4
e'4
f'4
}
.. container:: example
Markup can be tagged:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> markup = abjad.Markup(r"\markup \italic Allegro")
>>> abjad.attach(markup, staff[0], direction=abjad.UP, tag=abjad.Tag("RED:M1"))
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff, tags=True)
>>> print(string)
\new Staff
{
c'4
%! M1
%! RED
^ \markup \italic Allegro
d'4
e'4
f'4
}
Tagged markup can be deactivated:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> markup = abjad.Markup(r"\markup \italic Allegro")
>>> abjad.attach(
... markup,
... staff[0],
... deactivate=True,
... direction=abjad.UP,
... tag=abjad.Tag("RED:M1"),
... )
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff, tags=True)
>>> print(string)
\new Staff
{
c'4
%! M1
%! RED
%@% ^ \markup \italic Allegro
d'4
e'4
f'4
}
.. container:: example
REGRESSION: markup 1 doesn't disappear after markup 2 is attached:
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> markup_1 = abjad.Markup(r"\markup \italic Allegro")
>>> markup_2 = abjad.Markup(r'\markup \italic "non troppo"')
>>> abjad.attach(markup_1, staff[0], direction=abjad.UP)
>>> abjad.attach(markup_2, staff[0], direction=abjad.UP)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
^ \markup \italic "non troppo"
^ \markup \italic Allegro
d'4
e'4
f'4
}
"""
string: str
directed: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.string, str), repr(self.string)
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format(wrapper=wrapper)
contributions.after.markup.append(string)
return contributions
def _get_lilypond_format(self, *, wrapper=None):
string = self.string
if wrapper:
direction = wrapper.direction or "-"
direction = _string.to_tridirectional_lilypond_symbol(direction)
string = rf"{direction} {self.string}"
return string
[docs]@functools.total_ordering
@dataclasses.dataclass(frozen=True, slots=True, unsafe_hash=True)
class MetronomeMark:
r"""
MetronomeMark.
.. container:: example
Initializes integer-valued metronome mark:
>>> score = abjad.Score()
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> score.append(staff)
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 4), 90)
>>> abjad.attach(mark, staff[0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo 4=90
c'8
d'8
e'8
f'8
}
>>
.. container:: example
Initializes rational-valued metronome mark:
>>> import fractions
>>> score = abjad.Score()
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> score.append(staff)
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 4), fractions.Fraction(272, 3))
>>> abjad.attach(mark, staff[0])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', score])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo \markup \abjad-metronome-mark-mixed-number-markup #2 #0 #1 #"90" #"2" #"3"
c'8
d'8
e'8
f'8
}
>>
Overrides rational-valued metronome mark with decimal string:
>>> score = abjad.Score()
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> score.append(staff)
>>> mark = abjad.MetronomeMark(
... abjad.Duration(1, 4),
... fractions.Fraction(272, 3),
... decimal="90.66",
... )
>>> abjad.attach(mark, staff[0])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', score])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo \markup \abjad-metronome-mark-markup #2 #0 #1 #"90.66"
c'8
d'8
e'8
f'8
}
>>
Overrides rational-valued metronome mark with exact decimal:
>>> score = abjad.Score()
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> score.append(staff)
>>> mark = abjad.MetronomeMark(
... abjad.Duration(1, 4),
... fractions.Fraction(901, 10),
... decimal=True,
... )
>>> abjad.attach(mark, staff[0])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', score])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo \markup \abjad-metronome-mark-markup #2 #0 #1 #"90.1"
c'8
d'8
e'8
f'8
}
>>
.. container:: example
Initializes from text, duration and range:
>>> score = abjad.Score()
>>> staff = abjad.Staff("c'8 d'8 e'8 f'8")
>>> score.append(staff)
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 4), (120, 133), "Quick")
>>> abjad.attach(mark, staff[0])
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo Quick 4=120-133
c'8
d'8
e'8
f'8
}
>>
.. container:: example
Custom markup:
>>> import fractions
>>> markup = abjad.MetronomeMark.make_tempo_equation_markup(
... abjad.Duration(1, 4),
... 67.5,
... )
>>> mark = abjad.MetronomeMark(
... reference_duration=abjad.Duration(1, 4),
... units_per_minute=fractions.Fraction(135, 2),
... custom_markup=markup,
... )
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
>>> score = abjad.Score([staff])
>>> abjad.attach(mark, staff[0])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', score])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo \markup \abjad-metronome-mark-markup #2 #0 #1 #"67.5"
c'4
d'4
e'4
f'4
}
>>
.. container:: example
Decimal overrides:
>>> import fractions
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 4), fractions.Fraction(272, 3))
>>> mark.decimal
False
>>> mark = abjad.MetronomeMark(
... abjad.Duration(1, 4), fractions.Fraction(272, 3), decimal="90.66",
... )
>>> mark.decimal
'90.66'
>>> mark = abjad.MetronomeMark(
... abjad.Duration(1, 4), fractions.Fraction(901, 10), decimal=True,
... )
>>> mark.decimal
True
.. container:: example
Set ``hide=True`` when metronome mark should not appear in output (but
should still determine effective metronome mark):
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> score = abjad.Score([staff])
>>> metronome_mark_1 = abjad.MetronomeMark(abjad.Duration(1, 4), 72)
>>> abjad.attach(metronome_mark_1, staff[0])
>>> metronome_mark_2 = abjad.MetronomeMark(
... textual_indication="Allegro",
... hide=True,
... )
>>> abjad.attach(metronome_mark_2, staff[2])
>>> abjad.show(score) # doctest: +SKIP
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\tempo 4=72
c'4
d'4
e'4
f'4
}
>>
>>> for leaf in abjad.iterate.leaves(staff):
... prototype = abjad.MetronomeMark
... leaf, abjad.get.effective(leaf, prototype)
...
(Note("c'4"), MetronomeMark(reference_duration=Duration(1, 4), units_per_minute=72, textual_indication=None, custom_markup=None, decimal=False, hide=False))
(Note("d'4"), MetronomeMark(reference_duration=Duration(1, 4), units_per_minute=72, textual_indication=None, custom_markup=None, decimal=False, hide=False))
(Note("e'4"), MetronomeMark(reference_duration=None, units_per_minute=None, textual_indication='Allegro', custom_markup=None, decimal=False, hide=True))
(Note("f'4"), MetronomeMark(reference_duration=None, units_per_minute=None, textual_indication='Allegro', custom_markup=None, decimal=False, hide=True))
.. container:: example
Equality testing:
>>> mark_1 = abjad.MetronomeMark(abjad.Duration(3, 32), 52)
>>> mark_2 = abjad.MetronomeMark(abjad.Duration(3, 32), 52)
>>> mark_1 == mark_2
True
>>> mark_2 == mark_1
True
.. container:: example
>>> mark_1 = abjad.MetronomeMark(abjad.Duration(3, 32), 52)
>>> mark_2 = abjad.MetronomeMark(abjad.Duration(6, 32), 104)
>>> mark_1 == mark_2
False
>>> mark_2 == mark_1
False
.. container:: example
>>> mark_1 = abjad.MetronomeMark(abjad.Duration(3, 32), 52, "Langsam")
>>> mark_2 = abjad.MetronomeMark(abjad.Duration(3, 32), 52, "Langsam")
>>> mark_3 = abjad.MetronomeMark(abjad.Duration(3, 32), 52, "Slow")
>>> mark_1 == mark_2
True
>>> mark_1 == mark_3
False
"""
reference_duration: _duration.Duration | None = None
units_per_minute: int | fractions.Fraction | None = None
textual_indication: str | None = None
custom_markup: Markup | None = None
decimal: bool | str = False
hide: bool = dataclasses.field(compare=False, default=False)
context: typing.ClassVar[str] = "Score"
# TODO: make this work:
# find_context_on_attach: typing.ClassVar[bool] = True
mutates_offsets_in_seconds: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "METRONOME_MARK"
persistent: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "before"
[docs] def __post_init__(self):
if self.reference_duration:
assert isinstance(self.reference_duration, _duration.Duration), repr(
self.reference_duration
)
if isinstance(self.units_per_minute, float):
raise Exception(
f"do not set units-per-minute to float ({self.units_per_minute});"
" use fraction with decimal override instead."
)
prototype = (int, fractions.Fraction, collections.abc.Sequence, type(None))
assert isinstance(self.units_per_minute, prototype)
if isinstance(self.units_per_minute, collections.abc.Sequence):
assert len(self.units_per_minute) == 2
item_prototype = (int, _duration.Duration)
assert all(isinstance(_, item_prototype) for _ in self.units_per_minute)
assert isinstance(self.textual_indication, str | type(None))
if self.custom_markup is not None:
assert isinstance(self.custom_markup, Markup)
if self.decimal is not None:
assert isinstance(self.decimal, bool | str), repr(self.decimal)
assert isinstance(self.hide, bool), repr(self.hide)
[docs] def __lt__(self, argument) -> bool:
"""
Is true when ``argument`` is a metronome mark with quarters per minute greater
than that of this metronome mark.
"""
assert isinstance(argument, type(self)), repr(argument)
self_quarters_per_minute = self.quarters_per_minute or 0
argument_quarters_per_minute = argument.quarters_per_minute or 0
assert isinstance(self_quarters_per_minute, int | float | fractions.Fraction)
assert isinstance(
argument_quarters_per_minute, (int, float, fractions.Fraction)
)
return self_quarters_per_minute < argument_quarters_per_minute
@property
def _dotted(self):
return self.reference_duration.lilypond_duration_string
@property
def _equation(self):
if self.reference_duration is None:
return
if isinstance(self.units_per_minute, tuple):
first, second = self.units_per_minute
string = f"{self._dotted}={first}-{second}"
return string
elif isinstance(self.units_per_minute, fractions.Fraction):
markup = MetronomeMark.make_tempo_equation_markup(
self.reference_duration,
self.units_per_minute,
decimal=self.decimal,
)
string = markup.string
return string
string = f"{self._dotted}={self.units_per_minute}"
return string
def _get_lilypond_format(self):
equation = None
if self.reference_duration is not None and self.units_per_minute is not None:
equation = self._equation
if self.custom_markup is not None:
return rf"\tempo {self.custom_markup.string}"
elif self.textual_indication and equation:
return rf"\tempo {self.textual_indication} {equation}"
elif equation:
return rf"\tempo {equation}"
elif self.textual_indication:
return rf"\tempo {self.textual_indication}"
else:
return r"\tempo \default"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
if not self.hide:
contributions.before.commands.append(self._get_lilypond_format())
return contributions
def _get_markup(self):
if self.custom_markup is not None:
return self.custom_markup
duration_log = int(math.log(self.reference_duration.denominator, 2))
stem_height = 1
string = "abjad-metronome-mark-markup"
string += f" #{duration_log}"
string += f" #{self.reference_duration.dot_count}"
string += f" #{stem_height}"
string += f' #"{self.units_per_minute}"'
markup = Markup(rf"\markup {string}")
return markup
def _get_markup_arguments(self):
assert self.custom_markup is None
duration_log = int(math.log(self.reference_duration.denominator, 2))
dot_count = self.reference_duration.dot_count
stem_height = 1
if not self.decimal:
return {
"duration_log": duration_log,
"dot_count": dot_count,
"stem_height": stem_height,
"base": self.units_per_minute,
}
if isinstance(self.decimal, str):
return (duration_log, dot_count, stem_height, self.decimal)
assert self.decimal is True, repr(self.decimal)
fraction = fractions.Fraction(self.units_per_minute)
n, d = fraction.numerator, fraction.denominator
base = n // d
n = n % d
return {
"duration_log": duration_log,
"dot_count": dot_count,
"stem_height": stem_height,
"base": base,
"n": n,
"d": d,
}
@property
def is_imprecise(self) -> bool:
"""
Is true if metronome mark is entirely textual or if metronome mark's
units_per_minute is a range.
.. container:: example
Imprecise metronome marks:
>>> duration = abjad.Duration(1, 4)
>>> abjad.MetronomeMark(textual_indication="Langsam").is_imprecise
True
>>> abjad.MetronomeMark(duration, (35, 50), "Langsam").is_imprecise
True
>>> abjad.MetronomeMark(duration, (35, 50)).is_imprecise
True
Precise metronome marks:
>>> abjad.MetronomeMark(duration, 60).is_imprecise
False
>>> abjad.MetronomeMark(duration, 60, "Langsam").is_imprecise
False
"""
if self.reference_duration is not None:
if self.units_per_minute is not None:
if not isinstance(self.units_per_minute, tuple):
return False
return True
@property
def quarters_per_minute(self) -> tuple | None | fractions.Fraction:
"""
Gets metronome mark quarters per minute.
.. container:: example
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 8), 52)
>>> mark.quarters_per_minute
Fraction(104, 1)
Gives tuple when metronome mark ``units_per_minute`` is a range.
Gives none when metronome mark is imprecise.
Gives fraction otherwise.
"""
if self.is_imprecise:
return None
if isinstance(self.units_per_minute, tuple):
low = (
_duration.Duration(1, 4)
/ self.reference_duration
* self.units_per_minute[0]
)
high = (
_duration.Duration(1, 4)
/ self.reference_duration
* self.units_per_minute[1]
)
return (low, high)
result = (
_duration.Duration(1, 4) / self.reference_duration * self.units_per_minute
)
return fractions.Fraction(result)
[docs] def duration_to_milliseconds(self, duration) -> _duration.Duration:
"""
Gets millisecond value of ``duration`` under a given metronome mark.
Dotted sixteenth lasts 1500 msec at quarter equals 60:
.. container:: example
>>> mark = abjad.MetronomeMark(abjad.Duration(1, 4), 60)
>>> mark.duration_to_milliseconds((3, 8))
Duration(1500, 1)
"""
assert isinstance(self.reference_duration, _duration.Duration)
denominator = self.reference_duration.denominator
numerator = self.reference_duration.numerator
whole_note_duration = fractions.Fraction(1000)
whole_note_duration *= fractions.Fraction(denominator, numerator)
whole_note_duration *= fractions.Fraction(60, self.units_per_minute)
duration = _duration.Duration(duration)
return _duration.Duration(duration * whole_note_duration)
[docs] @staticmethod
def make_tempo_equation_markup(
reference_duration, units_per_minute, *, decimal=False
) -> Markup:
r"""
Makes tempo equation markup.
Integer-valued metronome mark:
.. container:: example
>>> markup = abjad.MetronomeMark.make_tempo_equation_markup((1, 4), 90)
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', markup])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(markup)
>>> print(string)
\markup \abjad-metronome-mark-markup #2 #0 #1 #"90"
.. container:: example
Float-valued metronome mark:
>>> markup = abjad.MetronomeMark.make_tempo_equation_markup((1, 4), 90.1)
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', markup])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(markup)
>>> print(string)
\markup \abjad-metronome-mark-markup #2 #0 #1 #"90.1"
.. container:: example
Rational-valued metronome mark:
>>> import fractions
>>> markup = abjad.MetronomeMark.make_tempo_equation_markup(
... abjad.Duration(1, 4),
... fractions.Fraction(272, 3),
... )
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', markup])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(markup)
>>> print(string)
\markup \abjad-metronome-mark-mixed-number-markup #2 #0 #1 #"90" #"2" #"3"
"""
reference_duration_ = _duration.Duration(reference_duration)
log = reference_duration_.exponent
dots = reference_duration_.dot_count
stem = 1
if isinstance(
units_per_minute, fractions.Fraction
) and not _math.is_integer_equivalent_number(units_per_minute):
if decimal:
decimal_: float | str
if decimal is True:
decimal_ = float(units_per_minute)
else:
assert isinstance(decimal, str), repr(decimal)
decimal_ = decimal
markup = Markup(
r"\markup \abjad-metronome-mark-markup"
f' #{log} #{dots} #{stem} #"{decimal_}"'
)
else:
nonreduced = fractions.Fraction(units_per_minute)
base = int(nonreduced)
remainder = nonreduced - base
n, d = remainder.numerator, remainder.denominator
markup = Markup(
r"\markup \abjad-metronome-mark-mixed-number-markup"
f" #{log} #{dots} #{stem}"
f' #"{base}" #"{n}" #"{d}"'
)
else:
markup = Markup(
r"\markup \abjad-metronome-mark-markup"
f' #{log} #{dots} #{stem} #"{units_per_minute}"'
)
return markup
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Ottava:
r"""
LilyPond ``\ottava`` command.
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> ottava = abjad.Ottava(n=1)
>>> abjad.attach(ottava, staff[0])
>>> ottava = abjad.Ottava(n=0, site="after")
>>> abjad.attach(ottava, staff[-1])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\ottava 1
c'4
d'4
e'4
f'4
\ottava 0
}
"""
n: int = 0
_: dataclasses.KW_ONLY
# TODO: maybe implement "leak" instead of "site"?
site: str = "before"
context: typing.ClassVar[str] = "Staff"
# find_context_on_attach: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.n, int), repr(self.n)
assert isinstance(self.site, str), repr(self.site)
def _before_attach(self, deactivate, component):
for indicator in component._get_indicators():
if isinstance(indicator, type(self)) and indicator.site == self.site:
classname = type(component).__name__
message = f"can not attach {self!r} to {classname}:"
message += f"\n {indicator!r} is already attached to {classname}."
raise Exception(message)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
n = self.n or 0
string = rf"\ottava {n}"
if self.site in ("before", None):
contributions.before.commands.append(string)
else:
assert self.site == "after"
contributions.after.commands.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class RehearsalMark:
r"""
Rehearsal mark.
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> score = abjad.Score([staff])
>>> mark = abjad.RehearsalMark(number=1)
>>> abjad.attach(mark, staff[0])
>>> scheme = "#format-mark-box-alphabet"
>>> abjad.setting(score).markFormatter = scheme
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
\with
{
markFormatter = #format-mark-box-alphabet
}
<<
\new Staff
{
\mark #1
c'4
d'4
e'4
f'4
}
>>
.. container:: example
Tweaks:
>>> note = abjad.Note("c'4")
>>> staff = abjad.Staff([note])
>>> score = abjad.Score([staff], name="Score")
>>> mark = abjad.RehearsalMark(markup="A")
>>> bundle = abjad.bundle(mark, r"\tweak color #blue")
>>> abjad.attach(bundle, note)
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\context Score = "Score"
<<
\new Staff
{
\tweak color #blue
\mark A
c'4
}
>>
"""
markup: Markup | str | None = None
number: int | None = None
# find_context_on_attach: typing.ClassVar[bool] = True
context: typing.ClassVar[str] = "Score"
[docs] def __post_init__(self):
if self.markup is not None:
assert isinstance(self.markup, str | Markup), repr(self.markup)
if self.number is not None:
assert isinstance(self.number, int), repr(self.number)
def _get_lilypond_format(self):
if self.markup is not None:
result = rf"\mark {self.markup}"
elif self.number is not None:
result = rf"\mark #{self.number}"
else:
result = r"\mark \default"
return result
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format()
contributions.opening.commands.append(string)
return contributions
[docs] @staticmethod
def from_string(string) -> "RehearsalMark":
"""
Makes rehearsal mark from ``string``.
.. container:: example
>>> abjad.RehearsalMark.from_string("A")
RehearsalMark(markup=None, number=1)
.. container:: example
>>> abjad.RehearsalMark.from_string("AA")
RehearsalMark(markup=None, number=27)
.. container:: example
>>> abjad.RehearsalMark.from_string("AB")
RehearsalMark(markup=None, number=28)
.. container:: example
>>> abjad.RehearsalMark.from_string("BA")
RehearsalMark(markup=None, number=53)
.. container:: example
>>> abjad.RehearsalMark.from_string("BB")
RehearsalMark(markup=None, number=54)
"""
number = 0
for place, letter in enumerate(reversed(string)):
integer = ord(letter) - ord("A") + 1
multiplier = 26**place
integer *= multiplier
number += integer
return RehearsalMark(number=number)
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Repeat:
r"""
Repeat.
.. container:: example
Volta repeat:
>>> container = abjad.Container("c'4 d'4 e'4 f'4")
>>> staff = abjad.Staff([container])
>>> score = abjad.Score([staff])
>>> repeat = abjad.Repeat()
>>> abjad.attach(repeat, container)
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\repeat volta 2
{
c'4
d'4
e'4
f'4
}
}
>>
.. container:: example
Unfold repeat:
>>> container = abjad.Container("c'4 d'4 e'4 f'4")
>>> staff = abjad.Staff([container])
>>> score = abjad.Score([staff])
>>> repeat = abjad.Repeat(repeat_type='unfold')
>>> abjad.attach(repeat, container)
>>> abjad.show(score) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\repeat unfold 2
{
c'4
d'4
e'4
f'4
}
}
>>
"""
repeat_count: int = 2
repeat_type: str = "volta"
can_attach_to_containers: typing.ClassVar[bool] = True
format_leaf_children: typing.ClassVar[bool] = False
# find_context_on_attach: typing.ClassVar[bool] = True
context: typing.ClassVar[str] = "Score"
site: typing.ClassVar[str] = "before"
[docs] def __post_init__(self):
assert isinstance(self.repeat_count, int), repr(self.repeat_count)
assert isinstance(self.repeat_type, str), repr(self.repeat_type)
def _get_lilypond_format(self):
return rf"\repeat {self.repeat_type} {self.repeat_count}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.before.commands.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class RepeatTie:
r"""
LilyPond ``\repeatTie`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 c' d' d'", name="Voice")
>>> repeat_tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(repeat_tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\context Voice = "Voice"
{
c'4
c'4
- \tweak color #blue
\repeatTie
d'4
d'4
}
>>> abjad.get.indicators(voice[1], abjad.RepeatTie)
[RepeatTie()]
>>> wrapper = abjad.get.indicator(voice[1], abjad.RepeatTie, unwrap=False)
>>> wrapper.get_item()
Bundle(indicator=RepeatTie(), tweaks=(Tweak(string='- \\tweak color #blue', tag=None),))
>>> for leaf in voice:
... leaf, abjad.get.logical_tie(leaf)
...
(Note("c'4"), LogicalTie(items=[Note("c'4"), Note("c'4")]))
(Note("c'4"), LogicalTie(items=[Note("c'4"), Note("c'4")]))
(Note("d'4"), LogicalTie(items=[Note("d'4")]))
(Note("d'4"), LogicalTie(items=[Note("d'4")]))
.. container:: example
With ``direction`` unset:
>>> voice = abjad.Voice("c'4 c'4 c''4 c''4", name="Voice")
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[1])
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[3])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\context Voice = "Voice"
{
c'4
c'4
- \tweak color #blue
\repeatTie
c''4
c''4
- \tweak color #blue
\repeatTie
}
With ``direction=abjad.UP``:
>>> voice = abjad.Voice("c'4 c'4 c''4 c''4", name="Voice")
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[1], direction=abjad.UP)
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[3], direction=abjad.UP)
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\context Voice = "Voice"
{
c'4
c'4
- \tweak color #blue
^
\repeatTie
c''4
c''4
- \tweak color #blue
^
\repeatTie
}
With ``direction=abjad.DOWN``:
>>> voice = abjad.Voice("c'4 c'4 c''4 c''4", name="Voice")
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[1], direction=abjad.DOWN)
>>> tie = abjad.RepeatTie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[3], direction=abjad.DOWN)
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\context Voice = "Voice"
{
c'4
c'4
- \tweak color #blue
_
\repeatTie
c''4
c''4
- \tweak color #blue
_
\repeatTie
}
"""
directed: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
def _attachment_test_all(self, argument):
if not (
hasattr(argument, "written_pitch") or hasattr(argument, "written_pitches")
):
string = f"Must be note or chord (not {argument})."
return [string]
return True
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
strings = []
if wrapper.direction is not None:
string = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
strings.append(string)
strings.append(r"\repeatTie")
contributions.after.spanner_starts.extend(strings)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class ShortInstrumentName:
r"""
LilyPond ``\shortInstrumentName`` command.
.. container:: example
Set ``\shortInstrumentName`` to markup like this:
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
>>> short_instrument_name = abjad.ShortInstrumentName(r"\markup Vc.")
>>> abjad.attach(short_instrument_name, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\set Staff.shortInstrumentName =
\markup Vc.
c'4
d'4
e'4
f'4
}
Set ``\shortInstrumentName`` to custom function like this:
>>> staff = abjad.Staff("c'4 d'4 e'4 f'4")
>>> short_instrument_name = abjad.ShortInstrumentName(r"\my_custom_function")
>>> abjad.attach(short_instrument_name, staff[0])
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\set Staff.shortInstrumentName = \my_custom_function
c'4
d'4
e'4
f'4
}
"""
markup: typing.Union[str, Markup]
_: dataclasses.KW_ONLY
context: str = "Staff"
site: str = "before"
latent: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
redraw: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.markup, str | Markup), repr(self.markup)
assert isinstance(self.context, str), repr(self.context)
assert isinstance(self.site, str), repr(self.site)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
site = getattr(contributions, self.site)
site.commands.extend(self._get_lilypond_format())
return contributions
def _get_lilypond_format(self, context=None):
result = []
if isinstance(context, str):
pass
elif context is not None:
context = context.lilypond_type
else:
context = self._get_lilypond_type()
if isinstance(self.markup, Markup):
string = self.markup.string
else:
assert isinstance(self.markup, str)
string = self.markup
string = rf"\set {context!s}.shortInstrumentName = {string}"
result.append(string)
return result
def _get_lilypond_type(self):
if isinstance(self.context, type):
return self.context.__name__
elif isinstance(self.context, str):
return self.context
else:
return type(self.context).__name__
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StaffChange:
r"""
Staff change.
.. container:: example
>>> staff_group = abjad.StaffGroup()
>>> staff_group.lilypond_type = "PianoStaff"
>>> rh_staff = abjad.Staff("c'8 d'8 e'8 f'8", name="RH_Staff")
>>> lh_staff = abjad.Staff("s2", name="LH_Staff")
>>> staff_group.extend([rh_staff, lh_staff])
>>> staff_change = abjad.StaffChange("LH_Staff")
>>> abjad.attach(staff_change, rh_staff[2])
>>> abjad.show(staff_group) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff_group)
>>> print(string)
\new PianoStaff
<<
\context Staff = "RH_Staff"
{
c'8
d'8
\change Staff = LH_Staff
e'8
f'8
}
\context Staff = "LH_Staff"
{
s2
}
>>
"""
staff_name: str | bool
context: typing.ClassVar[str] = "Staff"
format_leaf_children: typing.ClassVar[bool] = False
site: typing.ClassVar[str] = "before"
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
[docs] def __post_init__(self):
assert isinstance(self.staff_name, str | bool), repr(self.staff_name)
if isinstance(self.staff_name, bool):
assert self.staff_name is False, repr(self.staff_name)
def _get_lilypond_format(self):
if self.staff_name is False:
return r"\change Staff = ##f"
return rf"\change Staff = {self.staff_name}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.opening.commands.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartBeam:
r"""
LilyPond ``[`` command.
.. container:: example
>>> voice = abjad.Voice("c'8 d' e' f'")
>>> start_beam = abjad.StartBeam()
>>> bundle = abjad.bundle(start_beam, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_beam = abjad.StopBeam()
>>> abjad.attach(stop_beam, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
- \tweak color #blue
[
d'8
e'8
f'8
]
}
.. container:: example
With ``direction=abjad.DOWN``:
>>> voice = abjad.Voice("c'8 d' e' f'")
>>> start_beam = abjad.StartBeam()
>>> bundle = abjad.bundle(start_beam, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0], direction=abjad.DOWN)
>>> stop_beam = abjad.StopBeam()
>>> abjad.attach(stop_beam, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
- \tweak color #blue
_ [
d'8
e'8
f'8
]
}
"""
context: typing.ClassVar[str] = "Voice"
directed: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "BEAM"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
def _add_direction(self, string, wrapper):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._add_direction("[", wrapper)
contributions.after.start_beam.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartGroup:
r"""
LilyPond ``\startGroup`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_group = abjad.StartGroup()
>>> bundle = abjad.bundle(start_group, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_group = abjad.StopGroup()
>>> abjad.attach(stop_group, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startGroup
d'4
e'4
f'4
\stopGroup
}
"""
context: typing.ClassVar[str] = "Voice"
nestable_spanner: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\startGroup"
contributions.after.spanner_starts.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartHairpin:
r"""
Hairpin indicator.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('p'), voice[0])
>>> abjad.attach(abjad.StartHairpin('<'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\p
\<
d'4
e'4
f'4
\f
}
.. container:: example
Crescendo:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('p'), voice[0])
>>> abjad.attach(abjad.StartHairpin('<'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\p
\<
d'4
e'4
f'4
\f
}
Crescendo dal niente:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('niente', hide=True), voice[0])
>>> abjad.attach(abjad.StartHairpin('o<'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
- \tweak circled-tip ##t
\<
d'4
e'4
f'4
\f
}
Subito crescendo:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('p'), voice[0])
>>> abjad.attach(abjad.StartHairpin('<|'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\p
- \tweak stencil #abjad-flared-hairpin
\<
d'4
e'4
f'4
\f
}
Subito crescendo dal niente:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('niente', hide=True), voice[0])
>>> abjad.attach(abjad.StartHairpin('o<|'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
- \tweak circled-tip ##t
- \tweak stencil #abjad-flared-hairpin
\<
d'4
e'4
f'4
\f
}
.. container:: example
Diminuendo:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('f'), voice[0])
>>> abjad.attach(abjad.StartHairpin('>'), voice[0])
>>> abjad.attach(abjad.Dynamic('p'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\f
\>
d'4
e'4
f'4
\p
}
Diminuendo al niente:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('f'), voice[0])
>>> abjad.attach(abjad.StartHairpin('>o'), voice[0])
>>> abjad.attach(abjad.Dynamic('niente', command=r'\!'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\f
- \tweak circled-tip ##t
\>
d'4
e'4
f'4
\!
}
Subito diminuendo:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('f'), voice[0])
>>> abjad.attach(abjad.StartHairpin('|>'), voice[0])
>>> abjad.attach(abjad.Dynamic('p'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\f
- \tweak stencil #abjad-flared-hairpin
\>
d'4
e'4
f'4
\p
}
Subito diminuendo al niente:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('f'), voice[0])
>>> abjad.attach(abjad.StartHairpin('|>o'), voice[0])
>>> abjad.attach(abjad.Dynamic('niente', command=r'\!'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\f
- \tweak circled-tip ##t
- \tweak stencil #abjad-flared-hairpin
\>
d'4
e'4
f'4
\!
}
.. container:: example
Constante:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('p'), voice[0])
>>> abjad.attach(abjad.StartHairpin('--'), voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\p
- \tweak stencil #constante-hairpin
\<
d'4
e'4
f'4
\f
}
.. container:: example
Tweaks:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> abjad.attach(abjad.Dynamic('p'), voice[0])
>>> start_hairpin = abjad.StartHairpin('<')
>>> bundle = abjad.bundle(start_hairpin, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> abjad.attach(abjad.Dynamic('f'), voice[-1])
>>> abjad.override(voice).DynamicLineSpanner.staff_padding = 4.5
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
\with
{
\override DynamicLineSpanner.staff-padding = 4.5
}
{
c'4
\p
- \tweak color #blue
\<
d'4
e'4
f'4
\f
}
"""
shape: str = "<"
context: typing.ClassVar[str] = "Voice"
crescendo_start: typing.ClassVar[str] = r"\<"
decrescendo_start: typing.ClassVar[str] = r"\>"
directed: typing.ClassVar[bool] = True
known_shapes = ("<", "o<", "<|", "o<|", ">", ">o", "|>", "|>o", "--")
parameter: typing.ClassVar[str] = "DYNAMIC"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "after"
spanner_start: typing.ClassVar[bool] = True
trend: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert self.shape in self.known_shapes, repr(self.shape)
def _add_direction(self, string, *, wrapper=None):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
@staticmethod
def _circled_tip():
return _overrides.LilyPondOverride(
grob_name="Hairpin",
once=True,
property_path="circled-tip",
value=True,
)
@staticmethod
def _constante_hairpin():
return _overrides.LilyPondOverride(
grob_name="Hairpin",
once=True,
property_path="stencil",
value="#constante-hairpin",
)
@staticmethod
def _flared_hairpin():
return _overrides.LilyPondOverride(
grob_name="Hairpin",
once=True,
property_path="stencil",
value="#abjad-flared-hairpin",
)
def _get_lilypond_format(self, *, wrapper=None):
strings = []
if "--" in self.shape:
override = self._constante_hairpin()
string = override.tweak_string()
strings.append(string)
if "o" in self.shape:
override = self._circled_tip()
string = override.tweak_string()
strings.append(string)
if "|" in self.shape:
override = self._flared_hairpin()
string = override.tweak_string()
strings.append(string)
if "<" in self.shape or "--" in self.shape:
string = self.crescendo_start
string = self._add_direction(string, wrapper=wrapper)
strings.append(string)
elif ">" in self.shape:
string = self.decrescendo_start
string = self._add_direction(string, wrapper=wrapper)
strings.append(string)
else:
raise ValueError(self.shape)
return strings
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
strings = self._get_lilypond_format(wrapper=wrapper)
contributions.after.spanner_starts.extend(strings)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartPhrasingSlur:
r"""
LilyPond ``(`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_phrasing_slur = abjad.StartPhrasingSlur()
>>> bundle = abjad.bundle(start_phrasing_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_phrasing_slur = abjad.StopPhrasingSlur()
>>> abjad.attach(stop_phrasing_slur, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\(
d'4
e'4
f'4
\)
}
"""
context: typing.ClassVar[str] = "Voice"
directed: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "PHRASING_SLUR"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
def _add_direction(self, string, *, wrapper=None):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._add_direction(r"\(", wrapper=wrapper)
contributions.after.spanner_starts.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartPianoPedal:
r"""
LilyPond ``\sustainOn``, ``\sostenutoOn``, ``\unaCorda`` commands.
.. container:: example
>>> staff = abjad.Staff("c'4 d' e' r")
>>> start_piano_pedal = abjad.StartPianoPedal()
>>> bundle = abjad.bundle(
... start_piano_pedal,
... r"- \tweak color #blue",
... r"- \tweak parent-alignment-X #center",
... )
>>> abjad.attach(bundle, staff[0], context="Staff")
>>> stop_piano_pedal = abjad.StopPianoPedal()
>>> abjad.attach(stop_piano_pedal, staff[1], context="Staff")
>>> start_piano_pedal = abjad.StartPianoPedal()
>>> bundle = abjad.bundle(start_piano_pedal, r"- \tweak color #red")
>>> abjad.attach(bundle, staff[1], context="Staff")
>>> stop_piano_pedal = abjad.StopPianoPedal()
>>> abjad.attach(stop_piano_pedal, staff[2], context="Staff")
>>> start_piano_pedal = abjad.StartPianoPedal()
>>> bundle = abjad.bundle(start_piano_pedal, r"- \tweak color #green")
>>> abjad.attach(bundle, staff[2], context="Staff")
>>> stop_piano_pedal = abjad.StopPianoPedal()
>>> abjad.attach(stop_piano_pedal, staff[3], context="Staff")
>>> abjad.override(staff).SustainPedalLineSpanner.staff_padding = 5
>>> abjad.setting(staff).pedalSustainStyle = "#'mixed"
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override SustainPedalLineSpanner.staff-padding = 5
pedalSustainStyle = #'mixed
}
{
c'4
- \tweak color #blue
- \tweak parent-alignment-X #center
\sustainOn
d'4
\sustainOff
- \tweak color #red
\sustainOn
e'4
\sustainOff
- \tweak color #green
\sustainOn
r4
\sustainOff
}
"""
# TODO: change "kind" to "command" with "\unaCorda", "\sostenutoOn", "\sustainOn"
kind: str = "sustain"
context: typing.ClassVar[str] = "StaffGroup"
parameter: typing.ClassVar[str] = "PEDAL"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert self.kind in ("sustain", "sostenuto", "corda"), repr(self.kind)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
if self.kind == "corda":
string = r"\unaCorda"
elif self.kind == "sostenuto":
string = r"\sostenutoOn"
else:
assert self.kind == "sustain"
string = r"\sustainOn"
contributions.after.spanner_starts.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartSlur:
r"""
LilyPond ``(`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_slur = abjad.StartSlur()
>>> bundle = abjad.bundle(start_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_slur = abjad.StopSlur()
>>> abjad.attach(stop_slur, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
(
d'4
e'4
f'4
)
}
.. container:: example
With ``direction`` unset:
>>> voice = abjad.Voice("c'8 d' e' f' c'' d'' e'' f''")
>>> abjad.attach(abjad.StartSlur(), voice[0])
>>> abjad.attach(abjad.StopSlur(), voice[3])
>>> abjad.attach(abjad.StartSlur(), voice[4])
>>> abjad.attach(abjad.StopSlur(), voice[7])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
(
d'8
e'8
f'8
)
c''8
(
d''8
e''8
f''8
)
}
With ``direction=abjad.UP``:
>>> voice = abjad.Voice("c'8 d' e' f' c'' d'' e'' f''")
>>> abjad.attach(abjad.StartSlur(), voice[0], direction=abjad.UP)
>>> abjad.attach(abjad.StopSlur(), voice[3])
>>> abjad.attach(abjad.StartSlur(), voice[4], direction=abjad.UP)
>>> abjad.attach(abjad.StopSlur(), voice[7])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
^ (
d'8
e'8
f'8
)
c''8
^ (
d''8
e''8
f''8
)
}
With ``direction=abjad.DOWN``:
>>> voice = abjad.Voice("c'8 d' e' f' c'' d'' e'' f''")
>>> abjad.attach(abjad.StartSlur(), voice[0], direction=abjad.DOWN)
>>> abjad.attach(abjad.StopSlur(), voice[3])
>>> abjad.attach(abjad.StartSlur(), voice[4], direction=abjad.DOWN)
>>> abjad.attach(abjad.StopSlur(), voice[7])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
_ (
d'8
e'8
f'8
)
c''8
_ (
d''8
e''8
f''8
)
}
"""
context: typing.ClassVar[str] = "Voice"
directed: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "SLUR"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
def _add_direction(self, string, *, wrapper=None):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._add_direction("(", wrapper=wrapper)
contributions.after.spanner_starts.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartTextSpan:
r"""
LilyPond ``\startTextSpan`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-solid-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-solid-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
.. container:: example
>>> abjad.StartTextSpan()
StartTextSpan(command='\\startTextSpan', concat_hspace_left=0.5, concat_hspace_right=None, left_broken_text=None, left_text=None, right_padding=None, right_text=None, style=None)
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(
... start_text_span,
... r"- \tweak color #blue",
... r"- \tweak staff-padding 2.5",
... )
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> start_text_span = abjad.StartTextSpan(
... command=r"\startTextSpanOne",
... left_text=abjad.Markup(r"\upright A"),
... right_text=abjad.Markup(r"\markup \upright B"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(
... start_text_span,
... r"- \tweak color #red",
... r"- \tweak staff-padding 6",
... )
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan(command=r"\stopTextSpanOne")
>>> abjad.attach(stop_text_span, voice[-1])
>>> markup = abjad.Markup(r"\markup SPACER")
>>> bundle = abjad.bundle(markup, r"- \tweak transparent ##t")
>>> abjad.attach(bundle, voice[0], direction=abjad.UP)
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak transparent ##t
^ \markup SPACER
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright A \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright B
- \tweak color #red
- \tweak staff-padding 6
\startTextSpanOne
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak color #blue
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
\stopTextSpanOne
}
(Spacer text included to prevent docs from clipping output.)
.. container:: example
String literals are allowed in place of markup:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> left_text = r'- \tweak bound-details.left.text \markup'
>>> left_text += r' \concat { \upright pont. \hspace #0.5 }'
>>> right_text = r'- \tweak bound-details.right.text \markup'
>>> right_text += r' \upright tasto'
>>> start_text_span = abjad.StartTextSpan(
... left_text=left_text,
... right_text=right_text,
... style=r"\abjad-solid-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-solid-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
.. container:: example
Styles:
>>> voice = abjad.Voice("c'4 d' e' fs'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
fs'4
\stopTextSpan
}
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... style=r"\abjad-dashed-line-with-hook",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-dashed-line-with-hook
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-invisible-line",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-invisible-line
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-solid-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-solid-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... style=r"\abjad-solid-line-with-hook",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-solid-line-with-hook
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
Styles constrained to ``'dashed-line-with-arrow'``, ``'dashed-line-with-hook'``,
``'invisible-line'``, ``'solid-line-with-arrow'``, ``'solid-line-with-hook'``,
none.
.. container:: example
REGRESSION. Tweaking the dash-fraction of the start text span works correctly:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(
... start_text_span,
... r"- \tweak dash-fraction 0.85",
... r"- \tweak staff-padding 2.5",
... )
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-1])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak dash-fraction 0.85
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
f'4
\stopTextSpan
}
"""
command: str = r"\startTextSpan"
concat_hspace_left: int | float = 0.5
concat_hspace_right: int | float | None = None
left_broken_text: bool | str | Markup | None = None
left_text: str | Markup | None = None
right_padding: int | float | None = None
right_text: str | Markup | None = None
style: str | None = None
allow_multiple_with_different_values: typing.ClassVar[bool] = True
context: typing.ClassVar[str] = "Voice"
directed: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "TEXT_SPANNER"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.command, str), repr(self.command)
assert isinstance(self.style, str | None), repr(self.style)
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
if self.style is not None:
contributions.after.spanner_starts.append(rf"- {self.style}")
if self.left_text:
string = self._get_left_text_directive()
contributions.after.spanner_starts.append(string)
if self.left_broken_text is not None:
string = self._get_left_broken_text_tweak()
contributions.after.spanner_starts.append(string)
if self.right_text:
string = self._get_right_text_tweak()
contributions.after.spanner_starts.append(string)
if self.right_padding:
string = self._get_right_padding_tweak()
contributions.after.spanner_starts.append(string)
string = self._add_direction(self.command, wrapper=wrapper)
contributions.after.spanner_starts.append(string)
return contributions
def _add_direction(self, string, *, wrapper=None):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
def _get_left_broken_text_tweak(self):
if isinstance(self.left_broken_text, str):
left_broken_text = self.left_broken_text
else:
left_broken_text = self.left_broken_text.string
override = _overrides.LilyPondOverride(
grob_name="TextSpanner",
property_path=("bound-details", "left-broken", "text"),
value=left_broken_text,
)
string = override.tweak_string(self)
return string
def _get_left_text_directive(self):
if isinstance(self.left_text, str):
return self.left_text
left_text_string = self.left_text.string
left_text_string = left_text_string.removeprefix(r"\markup").strip()
hspace_string = rf"\hspace #{self.concat_hspace_left}"
markup = Markup(rf"\markup \concat {{ {left_text_string} {hspace_string} }}")
override = _overrides.LilyPondOverride(
grob_name="TextSpanner",
property_path=("bound-details", "left", "text"),
value=markup.string,
)
string = override.tweak_string()
return string
def _get_right_padding_tweak(self):
override = _overrides.LilyPondOverride(
grob_name="TextSpanner",
property_path=("bound-details", "right", "padding"),
value=self.right_padding,
)
string = override.tweak_string()
return string
def _get_right_text_tweak(self):
if isinstance(self.right_text, str):
return self.right_text
if self.concat_hspace_right is not None:
number = self.concat_hspace_right
right_text = self.right_text.string
markup = Markup(rf"\markup \concat {{ {right_text} \hspace #{number} }}")
else:
markup = self.right_text
if isinstance(markup, str):
value = markup
else:
assert isinstance(markup, Markup)
value = markup.string
override = _overrides.LilyPondOverride(
grob_name="TextSpanner",
property_path=("bound-details", "right", "text"),
value=value,
)
string = override.tweak_string()
return string
def _get_start_command(self):
string = self._get_parameter_name()
return rf"\start{string}"
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StartTrillSpan:
r"""
LilyPond ``\startTrillSpan`` command.
.. container:: example
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_trill_span = abjad.StartTrillSpan()
>>> bundle = abjad.bundle(start_trill_span, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_trill_span = abjad.StopTrillSpan()
>>> abjad.attach(stop_trill_span, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startTrillSpan
d'4
e'4
f'4
\stopTrillSpan
}
.. container:: example
With interval:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_trill_span = abjad.StartTrillSpan(interval=abjad.NamedInterval("M2"))
>>> bundle = abjad.bundle(start_trill_span, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_trill_span = abjad.StopTrillSpan()
>>> abjad.attach(stop_trill_span, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
\pitchedTrill
c'4
- \tweak color #blue
\startTrillSpan d'
d'4
e'4
f'4
\stopTrillSpan
}
.. container:: example
With pitch:
>>> voice = abjad.Voice("c'4 d' e' f'")
>>> start_trill_span = abjad.StartTrillSpan(pitch=abjad.NamedPitch("C#4"))
>>> bundle = abjad.bundle(start_trill_span, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_trill_span = abjad.StopTrillSpan()
>>> abjad.attach(stop_trill_span, voice[-1])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
\pitchedTrill
c'4
- \tweak color #blue
\startTrillSpan cs'
d'4
e'4
f'4
\stopTrillSpan
}
"""
interval: str | _pitch.NamedInterval | None = None
pitch: str | _pitch.NamedPitch | None = None
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "TRILL"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_start: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
if self.interval is not None:
assert isinstance(self.interval, _pitch.NamedInterval), repr(self.interval)
if self.pitch is not None:
assert isinstance(self.pitch, _pitch.NamedPitch), repr(self.pitch)
def _get_contributions(self, *, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\startTrillSpan"
if self.interval or self.pitch:
contributions.opening.pitched_trill.append(r"\pitchedTrill")
if self.pitch:
pitch = self.pitch
else:
pitch = component.written_pitch + self.interval
string = string + f" {pitch.name}"
# NOTE: crucial to use after.trill_spanner_starts here:
contributions.after.trill_spanner_starts.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StemTremolo:
r"""
Stem tremolo.
.. container:: example
Sixteenth-note tremolo:
>>> note = abjad.Note("c'4")
>>> stem_tremolo = abjad.StemTremolo(16)
>>> abjad.attach(stem_tremolo, note)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
:16
.. container:: example
Thirty-second-note tremolo:
>>> note = abjad.Note("c'4")
>>> stem_tremolo = abjad.StemTremolo(32)
>>> abjad.attach(stem_tremolo, note)
>>> abjad.show(note) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(note)
>>> print(string)
c'4
:32
.. container:: example
REGRESSION. Consider a note, rest, chord to which a stem tremolo is attached.
When such a note, rest, chord splits into two notes, rests, chords then a stem
tremolo attaches to each of the resultant notes, rests, chords:
>>> staff = abjad.Staff("c'4 c'2.")
>>> abjad.attach(abjad.StemTremolo(), staff[1])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
c'2.
:16
}
>>> abjad.Meter.rewrite_meter(staff[:], abjad.Meter((3, 4)))
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
c'2
:16
~
c'4
:16
}
"""
tremolo_flags: int = 16
site: typing.ClassVar[str] = "after"
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.MIDDLE
[docs] def __post_init__(self):
if not _math.is_nonnegative_integer_power_of_two(self.tremolo_flags):
raise ValueError(f"nonnegative integer power of 2: {self.tremolo_flags!r}.")
def _get_lilypond_format(self):
return f":{self.tremolo_flags!s}"
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
contributions.after.stem_tremolos.append(self._get_lilypond_format())
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopBeam:
r"""
LilyPond ``]`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'8 d' e' r")
>>> start_beam = abjad.StartBeam()
>>> bundle = abjad.bundle(start_beam, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_beam = abjad.StopBeam()
>>> abjad.attach(stop_beam, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
- \tweak color #blue
[
d'8
e'8
]
r8
}
With leak:
>>> voice = abjad.Voice("c'8 d' e' r")
>>> start_beam = abjad.StartBeam()
>>> bundle = abjad.bundle(start_beam, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_beam = abjad.StopBeam(leak=True)
>>> abjad.attach(stop_beam, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
- \tweak color #blue
[
d'8
e'8
<>
]
r8
}
"""
leak: bool = dataclasses.field(default=False, compare=False)
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "BEAM"
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = "]"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.stop_beam.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopGroup:
r"""
LilyPond ``\stopGroup`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_group = abjad.StartGroup()
>>> bundle = abjad.bundle(start_group, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_group = abjad.StopGroup()
>>> abjad.attach(stop_group, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startGroup
d'4
e'4
\stopGroup
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_group = abjad.StartGroup()
>>> bundle = abjad.bundle(start_group, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_group = abjad.StopGroup(leak=True)
>>> abjad.attach(stop_group, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startGroup
d'4
e'4
<>
\stopGroup
r4
}
.. container:: example
REGRESSION. Leaked contributions appear last in postevent format site:
>>> voice = abjad.Voice("c'8 d' e' f' r2")
>>> abjad.beam(voice[:4])
>>> start_group = abjad.StartGroup()
>>> bundle = abjad.bundle(start_group, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_group = abjad.StopGroup(leak=True)
>>> abjad.attach(stop_group, voice[3])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
[
- \tweak color #blue
\startGroup
d'8
e'8
f'8
]
<>
\stopGroup
r2
}
The leaked text spanner above does not inadvertantly leak the beam.
"""
leak: bool = dataclasses.field(default=False, compare=False)
context: typing.ClassVar[str] = "Voice"
nestable_spanner: typing.ClassVar[bool] = True
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\stopGroup"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopHairpin:
r"""
LilyPond ``\!`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_hairpin = abjad.StartHairpin('<')
>>> bundle = abjad.bundle(start_hairpin, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_hairpin = abjad.StopHairpin()
>>> abjad.attach(stop_hairpin, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\<
d'4
e'4
\!
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_hairpin = abjad.StartHairpin('<')
>>> bundle = abjad.bundle(start_hairpin, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_hairpin = abjad.StopHairpin(leak=True)
>>> abjad.attach(stop_hairpin, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\<
d'4
e'4
<>
\!
r4
}
"""
leak: bool = dataclasses.field(default=False, compare=False)
context: typing.ClassVar[str] = "Voice"
# TODO: should these be uncommented?
# parameter: typing.ClassVar[str] = "DYNAMIC"
# persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\!"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopPhrasingSlur:
r"""
LilyPond ``\)`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_phrasing_slur = abjad.StartPhrasingSlur()
>>> bundle = abjad.bundle(start_phrasing_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_phrasing_slur = abjad.StopPhrasingSlur()
>>> abjad.attach(stop_phrasing_slur, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\(
d'4
e'4
\)
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_phrasing_slur = abjad.StartPhrasingSlur()
>>> bundle = abjad.bundle(start_phrasing_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_phrasing_slur = abjad.StopPhrasingSlur(leak=True)
>>> abjad.attach(stop_phrasing_slur, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\(
d'4
e'4
<>
\)
r4
}
.. container:: example
REGRESSION. Leaked contributions appear last in postevent format site:
>>> voice = abjad.Voice("c'8 d' e' f' r2")
>>> abjad.beam(voice[:4])
>>> start_phrasing_slur = abjad.StartPhrasingSlur()
>>> bundle = abjad.bundle(start_phrasing_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_phrasing_slur = abjad.StopPhrasingSlur(leak=True)
>>> abjad.attach(stop_phrasing_slur, voice[3])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
[
- \tweak color #blue
\(
d'8
e'8
f'8
]
<>
\)
r2
}
The leaked text spanner above does not inadvertantly leak the beam.
"""
leak: bool = dataclasses.field(default=False, compare=False)
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "PHRASING_SLUR"
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\)"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopPianoPedal:
r"""
LilyPond ``\sostenutoOff``, ``\sustainOff``, ``\treCorde`` commands.
.. container:: example
Without leak:
>>> staff = abjad.Staff("c'4 d' e' r")
>>> start_piano_pedal = abjad.StartPianoPedal()
>>> bundle = abjad.bundle(
... start_piano_pedal,
... r"- \tweak color #blue",
... r"- \tweak parent-alignment-X #center",
... )
>>> abjad.attach(bundle, staff[0], context="Staff")
>>> stop_piano_pedal = abjad.StopPianoPedal()
>>> bundle = abjad.bundle(
... stop_piano_pedal,
... r"- \tweak color #red",
... r"- \tweak parent-alignment-X #center",
... )
>>> abjad.attach(bundle, staff[-2], context="Staff")
>>> abjad.override(staff).SustainPedalLineSpanner.staff_padding = 5
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override SustainPedalLineSpanner.staff-padding = 5
}
{
c'4
- \tweak color #blue
- \tweak parent-alignment-X #center
\sustainOn
d'4
e'4
- \tweak color #red
- \tweak parent-alignment-X #center
\sustainOff
r4
}
With leak:
>>> staff = abjad.Staff("c'4 d' e' r")
>>> start_piano_pedal = abjad.StartPianoPedal()
>>> bundle = abjad.bundle(
... start_piano_pedal,
... r"- \tweak color #blue",
... r"- \tweak parent-alignment-X #center",
... )
>>> abjad.attach(bundle, staff[0], context="Staff")
>>> stop_piano_pedal = abjad.StopPianoPedal(leak=True)
>>> bundle = abjad.bundle(
... stop_piano_pedal,
... r"- \tweak color #red",
... r"- \tweak parent-alignment-X #center",
... )
>>> abjad.attach(bundle, staff[-2], context="Staff")
>>> abjad.override(staff).SustainPedalLineSpanner.staff_padding = 5
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
\with
{
\override SustainPedalLineSpanner.staff-padding = 5
}
{
c'4
- \tweak color #blue
- \tweak parent-alignment-X #center
\sustainOn
d'4
e'4
<>
- \tweak color #red
- \tweak parent-alignment-X #center
\sustainOff
r4
}
"""
# TODO: change "kind" to "command"
kind: str = "sustain"
leak: bool = dataclasses.field(default=False, compare=False)
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
context: typing.ClassVar[str] = "StaffGroup"
parameter: typing.ClassVar[str] = "PEDAL"
persistent: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert self.kind in ("sustain", "sostenuto", "corda")
assert isinstance(self.leak, bool), repr(self.leak)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
if self.kind == "corda":
command = r"\treCorde"
elif self.kind == "sostenuto":
command = r"\sostenutoOff"
else:
assert self.kind == "sustain"
command = r"\sustainOff"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(command)
else:
contributions.after.spanner_stops.append(command)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopSlur:
r"""
LilyPond ``)`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_slur = abjad.StartSlur()
>>> bundle = abjad.bundle(start_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_slur = abjad.StopSlur()
>>> abjad.attach(stop_slur, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
(
d'4
e'4
)
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_slur = abjad.StartSlur()
>>> bundle = abjad.bundle(start_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_slur = abjad.StopSlur(leak=True)
>>> abjad.attach(stop_slur, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
(
d'4
e'4
<>
)
r4
}
.. container:: example
REGRESSION. Leaked contributions appear last in postevent format site.
The leaked text spanner above does not inadvertantly leak the beam:
>>> voice = abjad.Voice("c'8 d' e' f' r2")
>>> abjad.beam(voice[:4])
>>> start_slur = abjad.StartSlur()
>>> bundle = abjad.bundle(start_slur, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_slur = abjad.StopSlur(leak=True)
>>> abjad.attach(stop_slur, voice[3])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'8
[
- \tweak color #blue
(
d'8
e'8
f'8
]
<>
)
r2
}
"""
leak: bool = dataclasses.field(default=False, compare=False)
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "SLUR"
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.leak, bool), repr(self.leak)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = ")"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopTextSpan:
r"""
LilyPond ``\stopTextSpan`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan()
>>> abjad.attach(stop_text_span, voice[-2])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
\stopTextSpan
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_text_span = abjad.StartTextSpan(
... left_text=abjad.Markup(r"\upright pont."),
... right_text=abjad.Markup(r"\markup \upright tasto"),
... style=r"\abjad-dashed-line-with-arrow",
... )
>>> bundle = abjad.bundle(start_text_span, r"- \tweak staff-padding 2.5")
>>> abjad.attach(bundle, voice[0])
>>> stop_text_span = abjad.StopTextSpan(leak=True)
>>> abjad.attach(stop_text_span, voice[-2])
>>> lilypond_file = abjad.LilyPondFile([r'\include "abjad.ily"', voice])
>>> abjad.show(lilypond_file) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \abjad-dashed-line-with-arrow
- \tweak bound-details.left.text \markup \concat { \upright pont. \hspace #0.5 }
- \tweak bound-details.right.text \markup \upright tasto
- \tweak staff-padding 2.5
\startTextSpan
d'4
e'4
<>
\stopTextSpan
r4
}
"""
command: str = r"\stopTextSpan"
leak: bool = dataclasses.field(default=False, compare=False)
context: typing.ClassVar[str] = "Voice"
enchained: typing.ClassVar[bool] = True
nestable_spanner: typing.ClassVar[bool] = True
parameter: typing.ClassVar[str] = "TEXT_SPANNER"
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.command, str), repr(self.command)
assert self.command.startswith("\\"), repr(self.command)
assert isinstance(self.leak, bool), repr(self.leak)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = self.command
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class StopTrillSpan:
r"""
LilyPond ``\stopTrillSpan`` command.
.. container:: example
Without leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_trill_span = abjad.StartTrillSpan()
>>> bundle = abjad.bundle(start_trill_span, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_trill_span = abjad.StopTrillSpan()
>>> abjad.attach(stop_trill_span, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startTrillSpan
d'4
e'4
\stopTrillSpan
r4
}
With leak:
>>> voice = abjad.Voice("c'4 d' e' r")
>>> start_trill_span = abjad.StartTrillSpan()
>>> bundle = abjad.bundle(start_trill_span, r"- \tweak color #blue")
>>> abjad.attach(bundle, voice[0])
>>> stop_trill_span = abjad.StopTrillSpan(leak=True)
>>> abjad.attach(stop_trill_span, voice[-2])
>>> abjad.show(voice) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(voice)
>>> print(string)
\new Voice
{
c'4
- \tweak color #blue
\startTrillSpan
d'4
e'4
<>
\stopTrillSpan
r4
}
"""
leak: bool = dataclasses.field(default=False, compare=False)
time_orientation: typing.ClassVar[_enums.Horizontal] = _enums.RIGHT
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "TRILL"
persistent: typing.ClassVar[bool] = True
spanner_stop: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert isinstance(self.leak, bool), repr(self.leak)
def _get_contributions(self, component=None):
contributions = _contributions.ContributionsBySite()
string = r"\stopTrillSpan"
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.after.spanner_stops.append(string)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class Tie:
r"""
LilyPond ``~`` command.
.. container:: example
>>> staff = abjad.Staff("c'4 c' d' d'")
>>> tie = abjad.Tie()
>>> bundle = abjad.bundle(tie, r"- \tweak color #blue")
>>> abjad.attach(bundle, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
- \tweak color #blue
~
c'4
d'4
d'4
}
>>> for leaf in staff:
... leaf, abjad.get.logical_tie(leaf)
...
(Note("c'4"), LogicalTie(items=[Note("c'4"), Note("c'4")]))
(Note("c'4"), LogicalTie(items=[Note("c'4"), Note("c'4")]))
(Note("d'4"), LogicalTie(items=[Note("d'4")]))
(Note("d'4"), LogicalTie(items=[Note("d'4")]))
.. container:: example
With ``direction`` unset:
>>> staff = abjad.Staff("c'4 c' c'' c''")
>>> abjad.attach(abjad.Tie(), staff[0])
>>> abjad.attach(abjad.Tie(), staff[2])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
~
c'4
c''4
~
c''4
}
With ``direction=abjad.UP``:
>>> staff = abjad.Staff("c'4 c' c'' c''")
>>> abjad.attach(abjad.Tie(), staff[0], direction=abjad.UP)
>>> abjad.attach(abjad.Tie(), staff[2], direction=abjad.UP)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
^ ~
c'4
c''4
^ ~
c''4
}
With ``direction=abjad.DOWN``:
>>> staff = abjad.Staff("c'4 c' c'' c''")
>>> abjad.attach(abjad.Tie(), staff[0], direction=abjad.DOWN)
>>> abjad.attach(abjad.Tie(), staff[2], direction=abjad.DOWN)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
c'4
_ ~
c'4
c''4
_ ~
c''4
}
"""
directed: typing.ClassVar[bool] = True
post_event: typing.ClassVar[bool] = True
def _add_direction(self, string, *, wrapper=None):
if wrapper.direction is not None:
symbol = _string.to_tridirectional_lilypond_symbol(wrapper.direction)
string = f"{symbol} {string}"
return string
def _attachment_test_all(self, argument):
if not (
hasattr(argument, "written_pitch") or hasattr(argument, "written_pitches")
):
string = f"Must be note or chord (not {argument})."
return [string]
return True
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._add_direction("~", wrapper=wrapper)
strings = [string]
contributions.after.spanner_starts.extend(strings)
return contributions
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class TimeSignature:
r"""
Time signature.
.. container:: example
>>> staff = abjad.Staff("c'8 d'8 e'8")
>>> score = abjad.Score([staff], name="Score")
>>> time_signature = abjad.TimeSignature((3, 8))
>>> abjad.attach(time_signature, staff[0])
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 3/8
c'8
d'8
e'8
}
.. container:: example
Create score-contexted time signatures like this:
>>> staff = abjad.Staff("c'8 d'8 e'8 c'8 d'8 e'8")
>>> score = abjad.Score([staff], name="Score")
>>> time_signature = abjad.TimeSignature((3, 8))
>>> abjad.attach(time_signature, staff[0], context="Score")
>>> score[:] = []
Score-contexted time signatures format behind comments when no Abjad score
container is found:
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
%%% \time 3/8 %%%
c'8
d'8
e'8
c'8
d'8
e'8
}
>>> abjad.show(staff) # doctest: +SKIP
Score-contexted time signatures format normally when an Abjad score container is
found:
>>> score = abjad.Score([staff])
>>> string = abjad.lilypond(score)
>>> print(string)
\new Score
<<
\new Staff
{
\time 3/8
c'8
d'8
e'8
c'8
d'8
e'8
}
>>
>>> abjad.show(score) # doctest: +SKIP
.. container:: example
Time signatures can be tagged:
>>> staff = abjad.Staff("c'8 d'8 e'8 c'8 d'8 e'8")
>>> score = abjad.Score([staff], name="Score")
>>> time_signature = abjad.TimeSignature((3, 8))
>>> abjad.attach(
... time_signature,
... staff[0],
... context="Score",
... tag=abjad.Tag("+PARTS"),
... )
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(score, tags=True)
>>> print(string)
\context Score = "Score"
<<
\new Staff
{
%! +PARTS
\time 3/8
c'8
d'8
e'8
c'8
d'8
e'8
}
>>
.. container:: example
Set ``hide=True`` time signature should not appear in output (but should still
determine effective time signature):
>>> staff = abjad.Staff("c'4 d' e' f'")
>>> score = abjad.Score([staff], name="Score")
>>> time_signature = abjad.TimeSignature((4, 4))
>>> abjad.attach(time_signature, staff[0])
>>> time_signature = abjad.TimeSignature((2, 4), hide=True)
>>> abjad.attach(time_signature, staff[2])
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\time 4/4
c'4
d'4
e'4
f'4
}
>>> for leaf in abjad.iterate.leaves(staff):
... prototype = abjad.TimeSignature
... leaf, abjad.get.effective(leaf, prototype)
...
(Note("c'4"), TimeSignature(pair=(4, 4), hide=False, partial=None))
(Note("d'4"), TimeSignature(pair=(4, 4), hide=False, partial=None))
(Note("e'4"), TimeSignature(pair=(2, 4), hide=True, partial=None))
(Note("f'4"), TimeSignature(pair=(2, 4), hide=True, partial=None))
"""
pair: tuple[int, int]
hide: bool = dataclasses.field(compare=False, default=False)
partial: _duration.Duration | None = None
check_effective_context: typing.ClassVar[bool] = True
context: typing.ClassVar[str] = "Score"
persistent: typing.ClassVar[bool] = True
site: typing.ClassVar[str] = "before"
[docs] def __post_init__(self):
assert isinstance(self.pair, tuple), repr(self.pair)
assert len(self.pair) == 2, repr(self.pair)
assert isinstance(self.pair[0], int)
assert isinstance(self.pair[1], int)
if self.partial is not None:
assert isinstance(self.partial, _duration.Duration), repr(self.partial)
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
if self.is_non_dyadic_rational and not self.hide:
string = '#(ly:expect-warning "strange time signature found")'
contributions.before.commands.append(string)
strings = self._get_lilypond_format()
contributions.opening.commands.extend(strings)
return contributions
def _get_lilypond_format(self):
result = []
if self.hide:
return result
if self.partial is None:
result.append(rf"\time {self.numerator}/{self.denominator}")
else:
duration_string = self.partial.lilypond_duration_string
partial_directive = rf"\partial {duration_string}"
result.append(partial_directive)
string = rf"\time {self.numerator}/{self.denominator}"
result.append(string)
return result
@property
def denominator(self) -> int:
"""
Gets denominator of time signature:
.. container:: example
>>> abjad.TimeSignature((3, 8)).denominator
8
"""
return self.pair[1]
@property
def duration(self) -> _duration.Duration:
"""
Gets duration of time signature.
.. container:: example
>>> abjad.TimeSignature((3, 8)).duration
Duration(3, 8)
"""
return _duration.Duration(*self.pair)
@property
def is_non_dyadic_rational(self) -> bool:
r"""
Is true when time signature has non-power-of-two denominator.
.. container:: example
With non-power-of-two denominator:
>>> time_signature = abjad.TimeSignature((7, 12))
>>> time_signature.is_non_dyadic_rational
True
With power-of-two denominator:
>>> time_signature = abjad.TimeSignature((3, 8))
>>> time_signature.is_non_dyadic_rational
False
Suppresses LilyPond "strange time signature" warning:
>>> tuplet = abjad.Tuplet((2, 3), "c'4 d' e' f'")
>>> staff = abjad.Staff([tuplet])
>>> score = abjad.Score([staff], name="Score")
>>> time_signature = abjad.TimeSignature((4, 3))
>>> abjad.attach(time_signature, tuplet[0])
>>> abjad.show(staff) # doctest: +SKIP
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
\tweak edge-height #'(0.7 . 0)
\times 2/3
{
#(ly:expect-warning "strange time signature found")
\time 4/3
c'4
d'4
e'4
f'4
}
}
"""
return not _math.is_nonnegative_integer_power_of_two(self.denominator)
@property
def implied_prolation(self) -> fractions.Fraction:
"""
Gets implied prolation of time signature.
.. container:: example
Implied prolation of dyadic time signature:
>>> abjad.TimeSignature((3, 8)).implied_prolation
Fraction(1, 1)
Implied prolation of nondyadic time signature:
>>> abjad.TimeSignature((7, 12)).implied_prolation
Fraction(2, 3)
"""
return _duration.Duration(1, self.denominator).implied_prolation
@property
def numerator(self) -> int:
"""
Gets numerator of time signature.
.. container:: example
>>> abjad.TimeSignature((3, 8)).numerator
3
"""
return self.pair[0]
[docs] @staticmethod
def from_string(string) -> "TimeSignature":
"""
Makes new time signature from fraction ``string``.
.. container:: example
>>> abjad.TimeSignature.from_string("6/8")
TimeSignature(pair=(6, 8), hide=False, partial=None)
"""
assert isinstance(string, str), repr(string)
parts = string.split("/")
assert len(parts) == 2, repr(parts)
numbers = [int(_) for _ in parts]
numerator, denominator = numbers
return TimeSignature((numerator, denominator))
[docs] def to_dyadic_rational(self) -> "TimeSignature":
"""
Makes new time signature equivalent to current time signature with power-of-two
denominator.
.. container:: example
>>> abjad.TimeSignature((1, 12)).to_dyadic_rational()
TimeSignature(pair=(1, 12), hide=False, partial=None)
>>> abjad.TimeSignature((2, 12)).to_dyadic_rational()
TimeSignature(pair=(2, 12), hide=False, partial=None)
>>> abjad.TimeSignature((3, 12)).to_dyadic_rational()
TimeSignature(pair=(2, 8), hide=False, partial=None)
>>> abjad.TimeSignature((4, 12)).to_dyadic_rational()
TimeSignature(pair=(4, 12), hide=False, partial=None)
>>> abjad.TimeSignature((5, 12)).to_dyadic_rational()
TimeSignature(pair=(5, 12), hide=False, partial=None)
>>> abjad.TimeSignature((6, 12)).to_dyadic_rational()
TimeSignature(pair=(4, 8), hide=False, partial=None)
>>> abjad.TimeSignature((1, 14)).to_dyadic_rational()
TimeSignature(pair=(1, 14), hide=False, partial=None)
>>> abjad.TimeSignature((2, 14)).to_dyadic_rational()
TimeSignature(pair=(2, 14), hide=False, partial=None)
>>> abjad.TimeSignature((3, 14)).to_dyadic_rational()
TimeSignature(pair=(3, 14), hide=False, partial=None)
>>> abjad.TimeSignature((4, 14)).to_dyadic_rational()
TimeSignature(pair=(4, 14), hide=False, partial=None)
>>> abjad.TimeSignature((5, 14)).to_dyadic_rational()
TimeSignature(pair=(5, 14), hide=False, partial=None)
>>> abjad.TimeSignature((6, 14)).to_dyadic_rational()
TimeSignature(pair=(6, 14), hide=False, partial=None)
"""
non_power_of_two_denominator = self.denominator
power_of_two_denominator = _math.greatest_power_of_two_less_equal(
non_power_of_two_denominator
)
pair = _duration.with_denominator(self.pair, power_of_two_denominator)
return type(self)(pair)
[docs]@dataclasses.dataclass(frozen=True, order=True, slots=True, unsafe_hash=True)
class VoiceNumber:
r"""
LilyPond ``\voiceOne``, ``\voiceTwo``, ``\voiceThree``, ``\voiceFour``,
``\oneVoice`` commands.
.. container:: example
>>> staff = abjad.Staff()
>>> voice_1 = abjad.Voice("g'8 a' b' c''")
>>> command = abjad.VoiceNumber(n=1)
>>> abjad.attach(command, voice_1[0])
>>> voice_2 = abjad.Voice("e'8 f' g' a'")
>>> command = abjad.VoiceNumber(n=2)
>>> abjad.attach(command, voice_2[0])
>>> container = abjad.Container([voice_1, voice_2], simultaneous=True)
>>> staff.append(container)
>>> voice = abjad.Voice("c''4 a'")
>>> command = abjad.VoiceNumber()
>>> abjad.attach(command, voice[0])
>>> staff.append(voice)
>>> abjad.show(staff) # doctest: +SKIP
.. docs::
>>> string = abjad.lilypond(staff)
>>> print(string)
\new Staff
{
<<
\new Voice
{
\voiceOne
g'8
a'8
b'8
c''8
}
\new Voice
{
\voiceTwo
e'8
f'8
g'8
a'8
}
>>
\new Voice
{
\oneVoice
c''4
a'4
}
}
>>> for leaf in abjad.select.leaves(staff):
... command = abjad.get.effective(leaf, abjad.VoiceNumber)
... print(f"{leaf}, {command}")
Note("g'8"), VoiceNumber(n=1, leak=False)
Note("a'8"), VoiceNumber(n=1, leak=False)
Note("b'8"), VoiceNumber(n=1, leak=False)
Note("c''8"), VoiceNumber(n=1, leak=False)
Note("e'8"), VoiceNumber(n=2, leak=False)
Note("f'8"), VoiceNumber(n=2, leak=False)
Note("g'8"), VoiceNumber(n=2, leak=False)
Note("a'8"), VoiceNumber(n=2, leak=False)
Note("c''4"), VoiceNumber(n=None, leak=False)
Note("a'4"), VoiceNumber(n=None, leak=False)
"""
n: int | None = None
leak: bool = dataclasses.field(default=False, compare=False)
check_effective_context: typing.ClassVar[bool] = True
context: typing.ClassVar[str] = "Voice"
parameter: typing.ClassVar[str] = "VOICE_NUMBER"
persistent: typing.ClassVar[bool] = True
temporarily_do_not_check: typing.ClassVar[bool] = True
[docs] def __post_init__(self):
assert self.n in (1, 2, 3, 4, None), repr(self.n)
assert isinstance(self.leak, bool), repr(self.leak)
def _get_contributions(self, *, component=None, wrapper=None):
contributions = _contributions.ContributionsBySite()
string = self._get_lilypond_format()
if self.leak:
contributions.after.leak.append(_EMPTY_CHORD)
contributions.after.leaks.append(string)
else:
contributions.before.commands.append(string)
return contributions
def _get_lilypond_format(self):
if self.n == 1:
string = r"\voiceOne"
elif self.n == 2:
string = r"\voiceTwo"
elif self.n == 3:
string = r"\voiceThree"
elif self.n == 4:
string = r"\voiceFour"
else:
assert self.n is None
string = r"\oneVoice"
return string