from typing import Optional, List
from musicscore.quarterduration import QuarterDuration
from musicscore.util import isinstance_as_string
[docs]class QuantizeMixin:
_ATTRIBUTES = {'get_quantized'}
def __init__(self, get_quantized=None, *args, **kwargs):
super().__init__(*args, **kwargs)
self._possible_subdivisions = {}
self._get_quantized = None
self.get_quantized = get_quantized
def _get_beat_quarter_duration(self):
if isinstance_as_string(self, 'Beat'):
beat_quarter_duration = self.quarter_duration
else:
beat_quarter_duration = QuarterDuration(1)
return beat_quarter_duration
@property
def get_quantized(self) -> bool:
"""
:obj:`~musicscore.quantize.QuantizeMixin` property
- If ``get_quantized`` is set to None the first ``get_quantized`` of ancestors which is ``False`` or ``True`` will be returned.
- If :obj:`musicscore.score.Score.get_quantized` is set to None it will be converted to ``False``
- :obj:`musicscore.measure.Measure.finalize()` loops over all beats. If :obj:`musicscore.beat.Beat.get_quantized` returns ``True``
:obj:`musicscore.beat.Beat.quantize_quarter_durations()` is called.
:type: Optional[bool]
:rtype: bool
"""
if self._get_quantized is None:
if self.up:
return self.up.get_quantized
else:
return False
return self._get_quantized
@get_quantized.setter
def get_quantized(self, val):
self._get_quantized = val
[docs] def get_possible_subdivisions(self, beat_quarter_duration: Optional[QuarterDuration] = None) -> List[int]:
"""
:obj:`~musicscore.quantize.QuantizeMixin` method
This method is used by :obj:`~musicscore.beat.Beat`'s :obj:`~musicscore.beat.Beat.quantize_quarter_durations()`.
Possible subdivisions dictionary can be set with :obj:`~musicscore.quantize.QuantizeMixin.set_possible_subdivisions()`. Keys in the subdivisions dictionary correspond to beat quarter durations (e.g. 1, 1/2 etc.)
For example if get_possible_subdivisions()[1] == [3, 5, 8] a beat with a quarter duration of 1 can after quantization consists only of eighth triplets, quintuplets and 32ths.
If this dictionary is not set or ``beat_quarter_duration`` as key does not exist, the parent's possible subdivisions dictionary will be checked.
:obj:`~musicscore.score.Score` has a default :obj:`~musicscore.score.POSSIBLE_SUBDIVISIONS` dictionary which will be used if no other
musicscore node on the path from self to root has its own possible subdivisions dictionary with ``beat_quarter_duration`` as a
key. For setting possible subdivisions dictionary use always :obj:`~musicscore.quantize.QuantizeMixin.set_possible_subdivisions()`.
:param beat_quarter_duration: Used as key in possible subdivisions dictionary.
If ``None`` and self is a :obj:`~musicscore.beat.Beat` ``self.quarter_duration`` is used.
If ``None`` and self is not a :obj:`~musicscore.beat.Beat` it is set to 1.
:return: A list of possible subdivisions of a :obj:`~musicscore.beat.Beat`. This is used by beat's
:obj:`~musicscore.beat.Beat.quantize_quarter_durations()`
:rtype: List[int]
"""
if beat_quarter_duration is None:
beat_quarter_duration = self._get_beat_quarter_duration()
subdivisions = self._possible_subdivisions.get(beat_quarter_duration)
if subdivisions is None and self.up is not None and self.up.get_possible_subdivisions(
beat_quarter_duration) is not None:
subdivisions = self.up.get_possible_subdivisions(beat_quarter_duration)[:]
return subdivisions
[docs] def set_possible_subdivisions(self, subdivisions: list[int],
beat_quarter_duration: Optional[QuarterDuration] = None) -> None:
"""
:obj:`~musicscore.quantize.QuantizeMixin` method
This method is used to set or change possible subdivisions dictionary of a beat or its ascendants.
For example if get_possible_subdivisions()[1] == [3, 5, 8] a beat with a quarter duration of 1 can after quantization consists only of eighth triplets, quintuplets and 32ths.
:param subdivisions: list of possible subdivisions to be used in :obj:`musicscore.beat.Beat.quantize_quarter_durations()`
:param beat_quarter_duration: If ``None`` and self is a :obj:`~musicscore.beat.Beat` ``self.quarter_duration`` is used.
If ``None`` and self is not a :obj:`~musicscore.beat.Beat` it is set to 1.
:return: None
"""
if beat_quarter_duration is None:
beat_quarter_duration = self._get_beat_quarter_duration()
elif isinstance_as_string(self, 'Beat') and beat_quarter_duration != self.quarter_duration:
raise ValueError(
f"beat_quarter_duration '{beat_quarter_duration}' must be None or equal to the beat quarter_duration '{self.quarter_duration}'")
if not isinstance(beat_quarter_duration, QuarterDuration):
beat_quarter_duration = QuarterDuration(beat_quarter_duration)
try:
subdivisions = list(subdivisions)
except TypeError:
subdivisions = [subdivisions]
self._possible_subdivisions[beat_quarter_duration] = subdivisions