from typing import Optional, Union
import abjad
from ._HarmonicParent import _HarmonicParent
[docs]class ArtificialHarmonic(abjad.Chord, _HarmonicParent):
r"""Creates a chord with a tweaked top note head for notating artificial
harmonics. This is a child class of |abjad.Chord|.
Basic usage:
Usage is similar to |abjad.Chord|:
>>> harm = auxjad.ArtificialHarmonic("<g c'>4")
>>> harm.style
"#'harmonic"
>>> abjad.show(harm)
.. docs::
<
g
\tweak style #'harmonic
c'
>4
.. figure:: ../_images/ArtificialHarmonic-16am9cj6p9u.png
And similarly to |abjad.Chord|, pitch and duration can be input in
many different ways:
>>> harm1 = auxjad.ArtificialHarmonic(r"<g c'>4")
>>> harm2 = auxjad.ArtificialHarmonic(["g", "c'"], 1 / 4)
>>> harm3 = auxjad.ArtificialHarmonic([-5, 0], 0.25)
>>> harm4 = auxjad.ArtificialHarmonic([-5, 0], abjad.Duration(1, 4))
>>> staff = abjad.Staff([harm1, harm2, harm3, harm4])
>>> abjad.show(staff)
.. docs::
\new Staff
{
<
g
\tweak style #'harmonic
c'
>4
<
g
\tweak style #'harmonic
c'
>4
<
g
\tweak style #'harmonic
c'
>4
<
g
\tweak style #'harmonic
c'
>4
}
.. figure:: ../_images/ArtificialHarmonic-je277bmakgs.png
.. error::
It is important to note that this class can only be initialised with
exactly two pitches. Any other number of pitches will raise a
:exc:`ValueError`:
>>> auxjad.ArtificialHarmonic(r"<g c' d'>4")
ValueError: 'ArtificialHarmonic' requires exactly two
'written_pitches' for initialisation
:attr:`style`:
When instantiating this class, use the keyword argument :attr:`style`
to set a different type of note head for the top note, such as
``"#'harmonic-mixed"``:
>>> harm = auxjad.ArtificialHarmonic(r"<g c'>4",
... style="#'harmonic-mixed",
... )
>>> harm.style
"#'harmonic-mixed"
>>> abjad.show(harm)
.. docs::
<
g
\tweak style #'harmonic-mixed
c'
>4
.. figure:: ../_images/ArtificialHarmonic-ohqb65228iq.png
:attr:`is_parenthesized`:
To notate natural harmonics with a parenthesised pitch for the open
string at the bottom of the interval, set the keyword
:attr:`is_parenthesized` to ``True``.
>>> harm = auxjad.ArtificialHarmonic(r"<g c'>4",
... is_parenthesized=True,
... )
>>> harm.is_parenthesized
True
>>> abjad.show(harm)
.. docs::
<
\parenthesize
\tweak ParenthesesItem.font-size -4
g
\tweak style #'harmonic
c'
>4
.. figure:: ../_images/ArtificialHarmonic-2q3jkx33yvl.png
:attr:`~abjad.core.Chord.Chord.multiplier`:
Similarly to |abjad.Chord|, this class can take multipliers:
>>> harm = auxjad.ArtificialHarmonic(r"<g c'>4",
... multiplier=(2, 3),
... )
>>> harm.multiplier
abjad.Multiplier(2, 3)
>>> abjad.show(harm)
.. docs::
<
g
\tweak style #'harmonic
c'
>4 * 2/3
.. figure:: ../_images/ArtificialHarmonic-ouhdk3ugkcs.png
Properties:
All properties of |abjad.Chord| are also available to be read. This
class also includes two new properties named :attr:`style` and
:attr:`is_parenthesized`:
>>> harm = auxjad.ArtificialHarmonic(r"<g c'>4")
>>> harm.written_pitches
"g c'"
>>> harm.written_duration
1/4
>>> harm.style
"#'harmonic"
>>> harm.is_parenthesized
False
All these properties can be set to different values after
initialisation:
>>> harm.written_pitches = [-5, 2]
>>> harm.written_duration = abjad.Duration(1, 8)
>>> harm.style = "#'harmonic-mixed"
>>> harm.is_parenthesized = True
>>> harm.written_pitches
"g d'"
>>> harm.written_duration
1/8
>>> harm.style
"#'harmonic-mixed"
>>> harm.is_parenthesized
True
:meth:`sounding_pitch` and :meth:`sounding_note`:
The methods :meth:`sounding_pitch` and :meth:`sounding_note` return the
sounding pitch and sounding note, respectively. Their types are
|abjad.Pitch| and |abjad.Note|, respectively.
>>> harmonics = [ArtificialHarmonic(r"<g b>4"),
... ArtificialHarmonic(r"<g c'>4"),
... ArtificialHarmonic(r"<g d'>4"),
... ArtificialHarmonic(r"<g e'>4"),
... ArtificialHarmonic(r"<g g'>4"),
... ]
>>> for harmonic in harmonics:
... print(harmonic.sounding_pitch())
b''
g''
d''
b''
g'
>>> for harmonic in harmonics:
... print(harmonic.sounding_note())
b''4
g''4
d''4
b''4
g'4
:meth:`sounding_note` and indicators:
The note created by :meth:`sounding_note` inherits all indicators of
the artificial harmonic.
>>> harm = auxjad.ArtificialHarmonic(r"<g c'>4-.\pp")
>>> abjad.show(harm.sounding_note())
.. docs::
g''4
\pp
- \staccato
.. figure:: ../_images/ArtificialHarmonic-dfabdv155mu.png
.. error::
Both :meth:`sounding_pitch` and :meth:`sounding_note` methods raise a
:exc:`ValueError` exception when it cannot calculate the sounding pitch
for the given interval.
>>> harm = auxjad.ArtificialHarmonic(r"<g ef'>4")
>>> harm.sounding_pitch()
ValueError: cannot calculate sounding pitch for given interval
>>> harm.sounding_note()
ValueError: cannot calculate sounding pitch for given interval
:attr:`markup`:
To add a markup expression to the artificial harmonic, use the
:attr:`markup` optional keyword argument, which takes strings. By
default, the markup position is above the harmonic note, but this can
be overridden using the keyword :attr:`direction`, which can take
strings as well as ``abjad.Up`` and ``abjad.Down``:
>>> harm1 = auxjad.ArtificialHarmonic(r"<a d'>1")
>>> harm2 = auxjad.ArtificialHarmonic(r"<a d'>1",
... markup='I.',
... )
>>> harm3 = auxjad.ArtificialHarmonic(r"<a d'>1",
... markup='I.',
... direction=abjad.Down)
>>> staff = abjad.Staff([harm1, harm2, harm3])
>>> abjad.show(staff)
.. docs::
\new Staff
{
<
a
\tweak style #'harmonic
d'
>1
\once \override TextScript.parent-alignment-X = 0
\once \override TextScript.self-alignment-X = 0
<
a
\tweak style #'harmonic
d'
>1
^ \markup { I. }
\once \override TextScript.parent-alignment-X = 0
\once \override TextScript.self-alignment-X = 0
<
a
\tweak style #'harmonic
d'
>1
_ \markup { I. }
}
.. figure:: ../_images/ArtificialHarmonic-teysjphrrpn.png
Setting :attr:`markup` to ``None`` will remove the markup from the
note.
>>> harm = auxjad.ArtificialHarmonic(r"<a d'>1",
... markup='I.',
... )
>>> harm.markup = None
>>> abjad.show(harm)
.. docs::
<
a
\tweak style #'harmonic
d'
>1
.. figure:: ../_images/ArtificialHarmonic-nov336z64r.png
:attr:`centre_markup`:
When a markup expression is added to the harmonic note by using the
:attr:`markup` optional keyword argument, it will be automatically
centred above the note (as the main purpose of this markup is to show
string numbers). To disable this behaviour, set :attr:`centre_markup`
to ``False``. Compare:
>>> harm1 = auxjad.ArtificialHarmonic(r"<a d'>1",
... markup='III.',
... )
>>> abjad.show(harm1)
.. docs::
\once \override TextScript.parent-alignment-X = 0
\once \override TextScript.self-alignment-X = 0
<
bf'
\tweak style #'harmonic
ef''
>1
^ \markup { III. }
.. figure:: ../_images/ArtificialHarmonic-cFjQuNmS3p.png
>>> harm2 = auxjad.ArtificialHarmonic(r"<a d'>1",
... markup='III.',
... centre_markup=False,
... )
>>> abjad.show(harm2)
.. docs::
<
bf'
\tweak style #'harmonic
ef''
>1
^ \markup { III. }
.. figure:: ../_images/ArtificialHarmonic-gfhbSlQPlK.png
.. error::
If another markup is attached to the harmonic note, trying to set the
:attr:`markup` property to ``None`` will raise an :exc:`Exception`:
>>> harm = auxjad.ArtificialHarmonic(r"<a d'>1")
>>> abjad.attach(abjad.Markup('test'), harm)
>>> harm.markup = 'I.'
>>> harm.markup = None
Exception: multiple indicators attached to client.
"""
### CLASS VARIABLES ###
__slots__ = ('_style',
'_is_parenthesized',
'_direction',
'_centre_markup',
'_markup',
)
### INITIALISER ###
[docs] def __init__(self,
*arguments,
multiplier: Optional[abjad.typings.DurationTyping] = None,
tag: Optional[abjad.Tag] = None,
style: str = "#'harmonic",
is_parenthesized: bool = False,
markup: Optional[str] = None,
centre_markup: bool = True,
direction: Union[str, abjad.enums.VerticalAlignment] = 'up',
) -> None:
r'Initialises self.'
super().__init__(*arguments, multiplier=multiplier, tag=tag)
if len(self.written_pitches) != 2:
raise ValueError("'ArtificialHarmonic' requires exactly two "
"'written_pitches' for initialisation")
self.style = style
self.is_parenthesized = is_parenthesized
self._direction = direction
self.centre_markup = centre_markup
self.markup = markup
### PUBLIC METHODS ###
[docs] def sounding_pitch(self) -> abjad.Pitch:
r'Returns the sounding pitch of the harmonic as an |abjad.Pitch|.'
interval = abs(self.written_pitches[1]
- self.written_pitches[0]).semitones
sounding_pitch_dict = {1: 48,
2: 36,
3: 31,
4: 28,
5: 24,
7: 19,
9: 28,
12: 12,
16: 28,
19: 19,
24: 24,
28: 28,
}
try:
sounding_pitch = (self.written_pitches[0]
+ sounding_pitch_dict[interval])
except KeyError as err:
raise ValueError('cannot calculate sounding pitch for given '
'interval') from err
return sounding_pitch
[docs] def sounding_note(self) -> abjad.Note:
r'Returns the sounding note of the harmonic as an |abjad.Note|.'
note = abjad.Note(self.sounding_pitch(), self._written_duration)
for indicator in abjad.get.indicators(self):
abjad.attach(indicator, note)
return note
### PRIVATE METHODS ###
def _attach_centre_markup(self) -> None:
r'Attaches the centre markup tweaks.'
literal1 = abjad.LilyPondLiteral(
r'\once \override TextScript.parent-alignment-X = 0'
)
literal2 = abjad.LilyPondLiteral(
r'\once \override TextScript.self-alignment-X = 0'
)
abjad.attach(literal1, self)
abjad.attach(literal2, self)
def _detach_centre_markup(self) -> None:
r'Detaches the centre markup tweaks.'
literal1 = abjad.LilyPondLiteral(
r'\once \override TextScript.parent-alignment-X = 0'
)
literal2 = abjad.LilyPondLiteral(
r'\once \override TextScript.self-alignment-X = 0'
)
if abjad.get.indicator(self, literal1):
abjad.detach(literal1, self)
if abjad.get.indicator(self, literal2):
abjad.detach(literal2, self)
### PUBLIC PROPERTIES ###
@property
def written_pitches(self) -> abjad.pitch.segments.PitchSegment:
r'The written pitches of the two note heads.'
return abjad.pitch.segments.PitchSegment(
items=(note_head.written_pitch for note_head in self._note_heads),
item_class=abjad.pitch.pitches.NamedPitch,
)
@written_pitches.setter
def written_pitches(self,
written_pitches,
) -> None:
written_pitches_ = abjad.PitchSegment(written_pitches)
if len(written_pitches_) != 2:
raise ValueError("'ArtificialHarmonic' requires exactly two "
"pitches")
for index, pitch in enumerate(written_pitches_):
self._note_heads[index].written_pitch = pitch
@property
def style(self) -> str:
r'The style of the upper note head.'
return self._style
@style.setter
def style(self,
style: str,
) -> None:
if not isinstance(style, str):
raise TypeError("'style' must be 'str'")
self._style = style
abjad.tweak(self._note_heads[1]).style = self._style
@property
def centre_markup(self) -> bool:
r"""Tweaks the markup of the harmonic note head to be centred or not
as LilyPond doesn't centralises markups above note heads by default.
"""
return self._centre_markup
@centre_markup.setter
def centre_markup(self,
centre_markup: bool,
) -> None:
if not isinstance(centre_markup, bool):
raise TypeError("'style' must be 'bool'")
self._centre_markup = centre_markup
@property
def is_parenthesized(self) -> bool:
r'Whether the bottom note head is parenthesised or not.'
return self._is_parenthesized
@is_parenthesized.setter
def is_parenthesized(self,
is_parenthesized: bool,
) -> None:
if not isinstance(is_parenthesized, bool):
raise TypeError("'is_parenthesized' must be 'bool'")
self._is_parenthesized = is_parenthesized
self._note_heads[0].is_parenthesized = self._is_parenthesized
if self._is_parenthesized:
abjad.tweak(self._note_heads[0]).ParenthesesItem__font_size = -4
@property
def markup(self) -> str:
r'The markup of the harmonic note head.'
return self._markup
@markup.setter
def markup(self,
markup: str,
) -> None:
if markup is not None:
if not isinstance(markup, str):
raise TypeError("'markup' must be 'str'")
self._markup = markup
markup = abjad.Markup(self._markup,
direction=self._direction,
)
abjad.attach(markup, self)
if self._centre_markup:
self._attach_centre_markup()
else:
self._detach_centre_markup()
else:
self._markup = markup
if abjad.get.indicator(self, abjad.Markup):
abjad.detach(abjad.Markup, self)
self._detach_centre_markup()