Source code for musicscore.staff

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