from __future__ import annotations
from dataclasses import dataclass
from typing import Callable, Dict, Optional, Tuple, Union
from typing_extensions import TypeAlias
KeySignatureLayout: TypeAlias = Dict[str, Tuple[float, float]]
"""A layout specification for accidentals in key signatures.
Instances should have a key for every pitch letter a-g (lowercase),
and each value should be an ``(x, y)`` tuple of pseudo staff positions
to be plugged into a staff's unit.
"""
StaffPosFunc: TypeAlias = Callable[[int], float]
"""A function which takes a number of staff lines and returns a staff position."""
[docs]@dataclass(frozen=True)
class ClefType:
"""A logical clef specifier.
Many standard clefs are pre-defined in this module, but users can
also create custom clef types using this class.
"""
glyph_name: str
"""The SMuFL glyph name used to represent the clef."""
"""
how to deal with percussion clefs properly?
their staff position and middle C position both need to be
dynamically calculated from staff line count
"""
staff_pos: Union[float, StaffPosFunc]
"""Where the clef should be vertically drawn in a staff.
This is given in pseudo staff units relative to the staff's top
line. When positioning clefs, this should be passed into the
staff's unit to find the appropriate y position.
For clefs whose position depends on the number of staff lines,
this can be a ``StaffPosFunc``.
"""
middle_c_staff_pos: Union[float, StaffPosFunc]
"""Where this clef places middle C in a staff.
Like ``staff_pos``, this is given in pseudo staff units relative to
the staff's top line.
For clefs whose middle C position depends on the number of staff
lines, this can be a ``StaffPosFunc``.
"""
key_signature_flat_layout: Optional[KeySignatureLayout]
"""Set of positions for flat key sigantures, if applicable."""
key_signature_sharp_layout: Optional[KeySignatureLayout]
"""Set of positions for sharp key sigantures, if applicable."""
[docs] @classmethod
def from_def(cls, clef_type_def: ClefTypeDef) -> ClefType:
if isinstance(clef_type_def, ClefType):
return clef_type_def
return CLEF_TYPE_SHORTHAND_NAMES[clef_type_def.lower()]
_sharp_positions: KeySignatureLayout = {
"f": (0, 0),
"c": (1, 1.5),
"g": (2, -0.5),
"d": (3, 1),
"a": (4, 2.5),
"e": (5, 0.5),
"b": (6, 2),
}
_flat_positions: KeySignatureLayout = {
"b": (0, 2),
"e": (1, 0.5),
"a": (2, 2.5),
"d": (3, 1),
"g": (4, 3),
"c": (5, 1.5),
"f": (6, 3.5),
}
_bass_flat_positions = {
key: (value[0], value[1] + 1) for key, value in _flat_positions.items()
}
_tenor_flat_positions = {
key: (value[0], value[1] - 0.5) for key, value in _flat_positions.items()
}
_alto_flat_positions = {
key: (value[0], value[1] + 0.5) for key, value in _flat_positions.items()
}
_bass_sharp_positions = {
key: (value[0], value[1] + 1) for key, value in _sharp_positions.items()
}
_tenor_sharp_positions = {
"f": (0, 3),
"c": (1, 1),
"g": (2, 2.5),
"d": (3, 0.5),
"a": (4, 2),
"e": (5, 0),
"b": (6, 1.5),
}
_alto_sharp_positions = {
key: (value[0], value[1] + 0.5) for key, value in _sharp_positions.items()
}
TREBLE = ClefType("gClef", 3, 5, _flat_positions, _sharp_positions)
"""A conventional treble clef.
:meta hide-value:
"""
TREBLE_8VB = ClefType("gClef8vb", 3, 1.5, _flat_positions, _sharp_positions)
"""A treble clef one octave below, like often used in tenor vocal parts.
:meta hide-value:
"""
TREBLE_8VA = ClefType("gClef8va", 3, 8.5, _flat_positions, _sharp_positions)
"""A treble clef one octave below, like often used in tenor vocal parts.
:meta hide-value:
"""
TREBLE_8VB = ClefType("gClef8vb", 3, 1.5, _flat_positions, _sharp_positions)
"""A treble clef one octave below, like often used in tenor vocal parts.
:meta hide-value:
"""
BASS = ClefType("fClef", 1, -1, _bass_flat_positions, _bass_sharp_positions)
"""A conventional bass clef.
:meta hide-value:
"""
BASS_8VB = ClefType("fClef8vb", 1, -4.5, _bass_flat_positions, _bass_sharp_positions)
"""A bass clef one octave below, like often used in double bass parts.
:meta hide-value:
"""
TENOR = ClefType("cClef", 1, 1, _tenor_flat_positions, _tenor_sharp_positions)
"""A tenor C-clef.
:meta hide-value:
"""
ALTO = ClefType("cClef", 2, 2, _alto_flat_positions, _alto_sharp_positions)
"""An alto C-clef.
:meta hide-value:
"""
def _percussion_staff_pos_func(num_lines: int) -> float:
return (num_lines - 1) / 2
PERCUSSION_1 = ClefType(
"unpitchedPercussionClef1",
_percussion_staff_pos_func,
_percussion_staff_pos_func,
_alto_flat_positions,
_alto_sharp_positions,
)
"""A percussion clef consisting of 2 solid bars.
Percussion clefs are treated as C clefs always centered at the middle
of the staff. This works with any number of staff lines.
:meta hide-value:
"""
PERCUSSION_2 = ClefType(
"unpitchedPercussionClef2",
_percussion_staff_pos_func,
_percussion_staff_pos_func,
_alto_flat_positions,
_alto_sharp_positions,
)
"""A percussion clef consisting of an open rectangle.
See also ``PERCUSSION_1``
:meta hide-value:
"""
BRIDGE = ClefType(
"bridgeClef",
_percussion_staff_pos_func,
_percussion_staff_pos_func,
_alto_flat_positions,
_alto_sharp_positions,
)
"""A bridge clef.
Like percussion clefs, the bridge clef is treated as a C clef always centered at the
middle of the staff. This works with any number of staff lines.
:meta hide-value:
"""
CLEF_TYPE_SHORTHAND_NAMES = {
"treble": TREBLE,
"treble_8vb": TREBLE_8VB,
"treble_8va": TREBLE_8VA,
"bass": BASS,
"bass_8vb": BASS_8VB,
"tenor": TENOR,
"alto": ALTO,
"percussion_1": PERCUSSION_1,
"percussion_2": PERCUSSION_2,
"bridge": BRIDGE,
}
ClefTypeDef: TypeAlias = Union[ClefType, str]
"""ClefTypes can be given by the string name of any pre-defined ClefType in this module.
String lookup is case-insensitive.
"""