from __future__ import annotations
from typing import Optional
from neoscore.core.exceptions import DynamicStringError
from neoscore.core.music_font import MusicFont
from neoscore.core.music_text import MusicText
from neoscore.core.point import PointDef
from neoscore.core.positioned_object import PositionedObject
[docs]class Dynamic(MusicText):
"""A textual dynamic marking"""
# Map from letters to SMuFL canonical names
_dynamic_letter_map = {
"p": "dynamicPiano",
"m": "dynamicMezzo",
"f": "dynamicForte",
"r": "dynamicRinforzando",
"s": "dynamicSforzando",
"z": "dynamicZ",
"n": "dynamicNiente",
}
[docs] def __init__(
self,
pos: PointDef,
parent: PositionedObject,
text: str,
font: Optional[MusicFont] = None,
):
"""
Args:
pos: Position relative to ``parent``
parent: If no font is given, this or one of its ancestors must
implement :obj:`.HasMusicFont`.
text: A valid dynamic indicator string consisting
of the letters: 'p, m, f, r, s, z, n'
font: If provided, this overrides any font found in the ancestor chain.
"""
parsed_text = self._parse_dynamic_string(text)
MusicText.__init__(self, pos, parent, parsed_text, font)
[docs] @classmethod
def ppp(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'ppp' dynamic."""
return cls(pos, parent, "ppp", font)
[docs] @classmethod
def pp(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'pp' dynamic."""
return cls(pos, parent, "pp", font)
[docs] @classmethod
def p(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'p' dynamic."""
return cls(pos, parent, "p", font)
[docs] @classmethod
def mp(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create an 'mp' dynamic."""
return cls(pos, parent, "mp", font)
[docs] @classmethod
def mf(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create an 'mf' dynamic."""
return cls(pos, parent, "mf", font)
[docs] @classmethod
def f(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'f' dynamic."""
return cls(pos, parent, "f", font)
[docs] @classmethod
def ff(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'ff' dynamic."""
return cls(pos, parent, "ff", font)
[docs] @classmethod
def fff(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create a 'fff' dynamic."""
return cls(pos, parent, "fff", font)
[docs] @classmethod
def sfz(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create an 'sfz' dynamic."""
return cls(pos, parent, "sfz", font)
[docs] @classmethod
def fp(
cls, pos: PointDef, parent: PositionedObject, font: Optional[MusicFont] = None
) -> Dynamic:
"""Create an 'fp' dynamic."""
return cls(pos, parent, "fp", font)
@classmethod
def _parse_dynamic_string(cls, string: str) -> List[str]:
"""Parse a dynamics string into a list of SMuFL canonical names"""
music_chars = []
for char in string:
try:
music_chars.append(cls._dynamic_letter_map[char])
except KeyError:
raise DynamicStringError(string, char)
return music_chars