from typing import List
from musicscore.exceptions import MusicTreeTypeError
from musicscore.util import isinstance_as_string
from verysimpletree.tree import Tree
__all__ = ["MusicTree"]
[docs]class MusicTree(Tree):
"""
MusicTree is the parent class of all music tree objects:
- :obj:`~musicscore.score.Score` (root)
- :obj:`~musicscore.part.Part` (1st layer)
- :obj:`~musicscore.measure.Measure` (2nd layer)
- :obj:`~musicscore.staff.Staff` (3rd layer)
- :obj:`~musicscore.voice.Voice` (4th layer)
- :obj:`~musicscore.beat.Beat` (5th layer)
- :obj:`~musicscore.chord.Chord`, :obj:`~musicscore.chord.Rest` or :obj:`~musicscore.chord.GraceChord` (6th layer)
- :obj:`~musicscore.note.Note` (7th layer)
- :obj:`~musicscore.midi.Midi` (8th layer)
Midi can represent a pitch or a rest (value=0) and controls accidental sign of the pitch if necessary.
- :obj:`~musicscore.accidental.Accidental` (9th layer)
"""
_ATTRIBUTES = {"show_accidental_signs"}
default_show_accidental_signs = (
"modern" #: Class attribute of :obj:`~musicscore.musictree.MusicTree`
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._show_accidental_signs = None
@staticmethod
def _check_args_kwargs(args, kwargs, class_name, get_class_name=None):
for x in args:
if not isinstance(x, int) or x < 1:
raise TypeError(f"args {args} must be positive integers")
for x in kwargs.values():
if not isinstance(x, int) or x < 1:
raise TypeError(f"kwargs values {kwargs} must be positive integers")
def _get_default_keys():
default_keys = [
"part_number",
"measure_number",
"staff_number",
"voice_number",
"beat_number",
"chord_number",
]
class_names = [
"Score",
"Part",
"Measure",
"Staff",
"Voice",
"Beat",
"Chord",
]
class_index = class_names.index(class_name)
get_class_index = (
-1 if not get_class_name else class_names.index(get_class_name)
)
return default_keys[class_index:get_class_index]
default_keys = _get_default_keys()
if args and kwargs:
raise ValueError("Both args and kwargs cannot be set")
if args:
if len(args) != len(default_keys):
raise ValueError(
f"Wrong number of args {args}. Keys are: {default_keys}"
)
kwargs = {key: value for key, value in zip(default_keys, args)}
else:
keys = kwargs.keys()
if set(keys) != set(default_keys[: len(keys)]):
raise ValueError(
f"Wrong (number) of keys: {list(keys)} in kwargs. All Keys: {default_keys} must be set."
)
return kwargs
def _check_child_to_be_added(self, child):
if not isinstance_as_string(child, "MusicTree"):
raise MusicTreeTypeError(
f"MusicTree child must be of type MusicTree not {child.__class__}"
)
parent_child = {
"Score": "Part",
"Part": "Measure",
"Measure": "Staff",
"Staff": "Voice",
"Voice": "Beat",
"Beat": "Chord",
"GraceChord": "Note",
"Rest": "Note",
"Chord": "Note",
"Note": "Midi",
"Midi": "Accidental",
"C": "Accidental",
"D": "Accidental",
"E": "Accidental",
"F": "Accidental",
"G": "Accidental",
"A": "Accidental",
"B": "Accidental",
}
try:
if not isinstance_as_string(child, parent_child[self.__class__.__name__]):
raise MusicTreeTypeError(
f"{self.__class__.__name__} accepts only children of type {parent_child[self.__class__.__name__]} not "
f"{child.__class__.__name__}"
)
except KeyError:
raise NotImplementedError(
f"{self.__class__.__name__} add_child() not implemented."
)
def _get_kwargs(self, args_, kwargs_, get_class_name):
if isinstance_as_string(self, "Score"):
return self._check_args_kwargs(args_, kwargs_, "Score", get_class_name)
elif isinstance_as_string(self, "Part"):
return self._check_args_kwargs(args_, kwargs_, "Part", get_class_name)
elif isinstance_as_string(self, "Measure"):
return self._check_args_kwargs(args_, kwargs_, "Measure", get_class_name)
elif isinstance_as_string(self, "Staff"):
return self._check_args_kwargs(args_, kwargs_, "Staff", get_class_name)
elif isinstance_as_string(self, "Voice"):
return self._check_args_kwargs(args_, kwargs_, "Voice", get_class_name)
elif isinstance_as_string(self, "Beat"):
return self._check_args_kwargs(args_, kwargs_, "Beat", get_class_name)
elif isinstance_as_string(self, "Chord"):
return self._check_args_kwargs(args_, kwargs_, "Chord", get_class_name)
else:
raise MusicTreeTypeError(
f"MusicTree descendents of type {self.__class__} cannot use this method."
)
def _get_music_tree_descendent(self, args, kwargs, get_class_name):
kwargs = self._get_kwargs(args, kwargs, get_class_name)
if not kwargs:
raise TypeError
if len(kwargs) == 1:
try:
return self.get_children()[list(kwargs.values())[0] - 1]
except IndexError:
return None
else:
output = self
for key in kwargs:
string_to_eval = f"output.get_{key.split('_')[0]}(kwargs['{key}'])"
output = eval(string_to_eval)
if not output:
return None
return output
@property
def show_accidental_signs(self) -> str:
"""
:obj:`~musicscore.musictree.MusicTree` property
- If show_accidental_signs is set to None the first get_quantized of ancestors which is ``False`` or ``True`` will be returned.
- If :obj:`~musicscore.score.Score.show_accidental_signs` is set to None it will be converted to ``default_show_accidental_signs``
- Possible show_accidental_signs are: None, 'modern', 'traditional'
:type: Optional[str]
:rtype: str
"""
if self._show_accidental_signs is None:
if self.up:
return self.up.show_accidental_signs
else:
return self.default_show_accidental_signs
return self._show_accidental_signs
@show_accidental_signs.setter
def show_accidental_signs(self, val):
permitted = [None, "modern", "traditional"]
if val not in permitted:
raise ValueError(
f"show_accidental_signs {val} not in permitted: {permitted}"
)
self._show_accidental_signs = val
[docs] def get_beat(self, *args, **kwargs) -> "Beat":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`, :obj:`~musicscore.measure.Measure` and
:obj:`~musicscore.staff.Staff` and :obj:`~musicscore.voice.Voice`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``, ``measure_number``, ``staff_number``, ``voice_number``, ``beat_number`` depending on
musicscore's class. A :obj:`~musicscore.staff.Staff` for example needs ``voice_number`` and ``beat_number`` while a
:obj:`~musicscore.score.Score` needs all keyword arguments.
:rtype: :obj:`~musicscore.beat.Beat`
"""
return self._get_music_tree_descendent(args, kwargs, "Beat")
[docs] def get_chord(self, *args, **kwargs) -> "Chord":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`, :obj:`~musicscore.measure.Measure`,
:obj:`~musicscore.staff.Staff`, :obj:`~musicscore.voice.Voice` and :obj:`~musicscore.beat.Beat`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``, ``measure_number``, ``staff_number``, ``voice_number``, ``beat_number``, ``chord_number`` depending on
musicscore's class. A :obj:`~musicscore.staff.Staff` for example needs ``voice_number``, ``beat_number`` and
``chord_number`` while a :obj:`~musicscore.score.Score` needs all keyword arguments.
:rtype: :obj:`~musicscore.chord.Chord`
"""
return self._get_music_tree_descendent(args, kwargs, "Chord")
[docs] def get_beats(self) -> List["Beat"]:
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`, :obj:`~musicscore.measure.Measure`,
:obj:`~musicscore.staff.Staff` and :obj:`~musicscore.voice.Voice`.
:return: a flat list of all beats.
:rtype: List[:obj:`~musicscore.beat.Beat`]
"""
if isinstance_as_string(self, "Voice"):
return self.get_children()
else:
output = [ch for child in self.get_children() for ch in child.get_beats()]
if not output:
for cls_name in ["Beat", "Chord", "Note", "Midi", "Accidental"]:
if isinstance_as_string(self, cls_name):
raise MusicTreeTypeError(
f"MusicTree descendents of type {self.__class__} cannot use this method."
)
return output
[docs] def get_chords(self) -> List["Chord"]:
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`, :obj:`~musicscore.measure.Measure` and
:obj:`~musicscore.staff.Staff`, :obj:`~musicscore.voice.Voice` and :obj:`~musicscore.beat.Beat`
:return: a flat list of all chords.
:rtype: List[:obj:`~musicscore.chord.Chord`]
"""
if isinstance_as_string(self, "Beat"):
return self.get_children()
else:
output = [ch for child in self.get_children() for ch in child.get_chords()]
if not output:
for cls_name in ["Chord", "Note", "Midi", "Accidental"]:
if isinstance_as_string(self, cls_name):
raise MusicTreeTypeError(
f"MusicTree descendents of type {self.__class__} cannot use this method."
)
return output
[docs] def get_measure(self, *args, **kwargs) -> "Measure":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``, ``measure_number`` depending on musicscore's class.
:rtype: :obj:`~musicscore.measure.Measure`
"""
return self._get_music_tree_descendent(args, kwargs, "Measure")
[docs] def get_part(self, *args, **kwargs) -> "Part":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``.
:rtype: :obj:`~musicscore.part.Part`
"""
return self._get_music_tree_descendent(args, kwargs, "Part")
[docs] def get_staff(self, *args, **kwargs) -> "Staff":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score`, :obj:`~musicscore.part.Part` and :obj:`~musicscore.measure.Measure`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``, ``measure_number``, ``staff_number`` depending on musicscore's class.
:rtype: :obj:`~musicscore.staff.Staff`
"""
return self._get_music_tree_descendent(args, kwargs, "Staff")
[docs] def get_voice(self, *args, **kwargs) -> "Voice":
"""
:obj:`~musicscore.musictree.MusicTree` method
This method can be used for :obj:`~musicscore.score.Score` and :obj:`~musicscore.part.Part`, :obj:`~musicscore.measure.Measure` and
:obj:`~musicscore.staff.Staff`
:param args: can be used instead of ``kwargs``. A mixture of args and kwargs is not allowed.
:param kwargs: ``part_number``, ``measure_number``, ``staff_number``, ``voice_number`` depending on musicscore's class.
:rtype: :obj:`~musicscore.voice.Voice`
"""
return self._get_music_tree_descendent(args, kwargs, "Voice")