from typing import Optional, Set
from musicscore.clef import Clef
from musicscore.exceptions import (
StaffHasNoParentError,
AlreadyFinalizedError,
AddChordError,
)
from musicscore.finalize import FinalizeMixin
from musicscore.musictree import MusicTree
from musicscore.quantize import QuantizeMixin
from musicscore.tuplet import SimplifiedSextuplets
from musicscore.voice import Voice
from musicscore.xmlwrapper import XMLWrapper
from musicxml.xmlelement.xmlelement import XMLStaff
__all__ = ["Staff"]
[docs]class Staff(MusicTree, SimplifiedSextuplets, QuantizeMixin, FinalizeMixin, XMLWrapper):
"""
Parent type: :obj:`~musicscore.measure.Measure`
Child type: :obj:`~musicscore.voice.Voice`
"""
_ATTRIBUTES = {"clef", "default_clef", "number"}
_ATTRIBUTES = _ATTRIBUTES.union(MusicTree._ATTRIBUTES)
_ATTRIBUTES = _ATTRIBUTES.union(QuantizeMixin._ATTRIBUTES)
_ATTRIBUTES = _ATTRIBUTES.union(SimplifiedSextuplets._ATTRIBUTES)
XMLClass = XMLStaff
def __init__(
self,
number=None,
clef=None,
simplified_sextuplets=None,
get_quantized=None,
**kwargs,
):
super().__init__(
simplified_sextuplets=simplified_sextuplets, get_quantized=get_quantized
)
self._xml_object = self.XMLClass(value_=1, **kwargs)
self._number = None
self._clef = None
self.clef = clef
self.number = number
@property
def clef(self) -> Clef:
"""
:type: :obj:`~musicscore.clef.Clef`
:return: Clef associated with the staff.
:rtype: :obj:`~musicscore.clef.Clef`
"""
if not self._clef:
if self.get_previous_staff() and self.get_previous_staff().clef:
self._clef = self.get_previous_staff().clef.__copy__()
self._clef.show = False
return self._clef
@clef.setter
def clef(self, val):
if val is not None and not isinstance(val, Clef):
raise TypeError
if val and not val.number and self.clef:
val.number = self.clef.number
self._clef = val
@property
def number(self):
"""
:type: None or int. If None number is set to 1.
:return: positive int or None
"""
if self._number is not None:
return self.xml_object.value_
else:
return self._number
@number.setter
def number(self, val):
self._number = val
if val is None:
self.xml_object.value_ = 1
else:
self.xml_object.value_ = val
[docs] def add_child(self, child: Voice) -> Voice:
"""
- Adds a :obj:`~musicscore.voice.Voice` as child to staff.
- If voice number is ``None``, it is determined as length of children + 1.
- If voice number is already set an is not equal to length of children + 1. a ``ValueError`` is raised.
- If Staff has no parent :obj:`~musicscore.measure.Measure` a :obj:`~musicscore.exceptions.StaffHasNoParentError` is raised.
- After adding voice its :obj:`~musicscore.voice.Voice.update_beats()` is called.
:param child: :obj:`~musicscore.voice.Voice` , required
:return: added :obj:`~musicscore.voice.Voice`
"""
if self._finalized is True:
raise AlreadyFinalizedError(self, "add_child")
self._check_child_to_be_added(child)
if not self.up:
raise StaffHasNoParentError(
"A child Voice can only be added to a Staff if staff has a Measure parent."
)
if child.number is not None and child.number != len(self.get_children()) + 1:
raise ValueError(
f"Voice number must be None or {len(self.get_children()) + 1}"
)
if child.number is None:
child.number = len(self.get_children()) + 1
else:
child.number = len(self.get_children()) + 1
child._parent = self
self._children.append(child)
return child
def add_chord(self, *args, **kwargs):
raise AddChordError()
[docs] def add_voice(self, voice_number: Optional[int] = None) -> Voice:
"""
- Creates and adds a new :obj:`~musicscore.voice.Voice` object as child to staff.
- If voice number is greater than length of children + 1 all missing voices are created and added first.
:param voice_number: positive int or None. If ``None`` voice number is determined as length of children + 1.
:return: new :obj:`~musicscore.voice.Voice`
"""
if self._finalized is True:
raise AlreadyFinalizedError(self, "add_voice")
if voice_number is None:
voice_number = len(self.get_children()) + 1
voice_object = self.get_voice(voice_number=voice_number)
if voice_object is None:
for _ in range(voice_number - len(self.get_children())):
voice_object = self.add_child(Voice())
voice_object.update_beats()
return voice_object
voice_object.update_beats()
return voice_object
[docs] def fill_with_rests(self):
for voice in self.get_children():
voice.fill_with_rests()
[docs] def get_previous_staff(self) -> Optional["Staff"]:
"""
:return: :obj:`Staff` with the same number in previous :obj:`~musicscore.measure.Measure` if existing else ``None``
"""
if self.up and self.up.previous:
my_index = self.up.get_children().index(self)
try:
return self.up.previous.get_children()[my_index]
except IndexError:
return None
return None
[docs] def get_last_pitch_steps_with_accidentals(self) -> Set[str]:
"""
:return: A set of pitch steps with not natural accidental. This method is used to keep track of accidental signs which needs to
be shown hidden.
:rtype: Set[str]
"""
output = set()
for v in self.get_children():
if v.get_chords():
last_chord = v.get_chords()[-1]
if not last_chord.is_rest:
for m in last_chord.midis:
if m.accidental.sign != "natural":
step = m.accidental.get_pitch_parameters()[0]
if step not in output:
output.add(step)
return output