Source code for musicscore.musictree

from typing import List

from musicscore.exceptions import MusicTreeTypeError
from musicscore.util import isinstance_as_string
from tree.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')