Source code for musicscore.time

from typing import List, Optional

from musicscore.exceptions import TimeActualSignaturesNotValidError
from musicscore.quarterduration import QuarterDuration
from musicscore.util import isinstance_as_string
from musicxml.xmlelement.xmlelement import XMLTime, XMLBeats, XMLBeatType
from musicscore.xmlwrapper import XMLWrapper

__all__ = ['Time', 'flatten_times', 'CONVERSION_DICTIONARY']

#: If :obj:`Time.actual_signatures` is not set manually first this dictionary is used to create actual signature.
CONVERSION_DICTIONARY = {'2/8': [2, 8], '4/8': [2, 8, 2, 8], '5/8': [3, 8, 2, 8], '7/8': [4, 8, 3, 8]}


def _convert_signatures_to_ints(signatures):
    output = []
    for i in range(int(len(signatures) / 2)):
        beat = signatures[i * 2]
        beat_type = signatures[i * 2 + 1]
        if isinstance(beat, str):
            l = beat.split('+')
            if len(l) == 1:
                output.append(int(beat))
                output.append(int(beat_type))
            else:
                for x in l:
                    output.append(int(x))
                    output.append(int(beat_type))
        else:
            output.append(beat)
            output.append(int(beat_type))
    if not output:
        raise TimeActualSignaturesNotValidError
    return output


def _get_quarter_durations_from_ints(signatures):
    output = 0
    for i in range(int(len(signatures) / 2)):
        beat = signatures[i * 2]
        beat_type = signatures[i * 2 + 1]
        output += 4 * beat / beat_type
    return output


[docs]class Time(XMLWrapper): _ATTRIBUTES = {'signatures', 'actual_signatures', 'parent_measure', 'show'} XMLClass = XMLTime def __init__(self, *signatures, show=True, **kwargs): super().__init__() self._xml_object = self.XMLClass(**kwargs) self._parent_measure = None self._signatures = None self._actual_signatures = None self._intern_actual_signatures = None self._show = None self.signatures = signatures self.show = show def _calculate_actual_signatures(self): signatures = _convert_signatures_to_ints(self.signatures) signatures = [signatures[i:i + 2] for i in range(0, len(signatures), 2)] self._intern_actual_signatures = [] for signature in signatures: key = "/".join([str(x) for x in signature]) if key in CONVERSION_DICTIONARY: self._intern_actual_signatures.extend(CONVERSION_DICTIONARY[key]) elif signature[1] % 8 == 0 and signature[0] % 3 == 0: self._intern_actual_signatures.extend([3, signature[1]] * (signature[0] // 3)) else: self._intern_actual_signatures.extend([1, signature[1]] * signature[0]) return self._intern_actual_signatures def _reset_actual_signatures(self) -> None: """ Resets actual signatures to None. """ self._actual_signatures = None self._intern_actual_signatures = None def _update_signature_objects(self): signatures = [self.signatures[i:i + 2] for i in range(0, len(self.signatures), 2)] for beats, beat_type in zip(self._xml_object.find_children('XMLBeats'), self._xml_object.find_children('XMLBeatType')): if signatures: signature = signatures.pop(0) beats.value_ = str(signature[0]) beat_type.value_ = str(signature[1]) else: beats.up.remove(beats) beat_type.up.remove(beat_type) for beats, beat_type in signatures: self._xml_object.add_child(XMLBeats(str(beats))) self._xml_object.add_child(XMLBeatType(str(beat_type))) @property def actual_signatures(self) -> List[int]: """ Set and gets actual signatures. If :obj:`parent_measure` exists its beats inside voices will be updated. :return: A list of int representing actual time signatures. If not set manually, it is calculated internally. For example a 4/4 time signature gets automatically [1, 4, 1, 4, 1, 4, 1, 4] as actual_signatures if not set otherwise. .. seealso:: :obj:`CONVERSION_DICTIONARY` """ if self._actual_signatures is None: if self._intern_actual_signatures is None: self._calculate_actual_signatures() return self._intern_actual_signatures return self._actual_signatures @actual_signatures.setter def actual_signatures(self, val): if val is not None: val = _convert_signatures_to_ints(val) self._actual_signatures = val if self.parent_measure: self.parent_measure._update_voice_beats() @property def parent_measure(self) -> Optional["Measure"]: """ Set and get parent :obj:`~musicscore.measure.Measure`. """ return self._parent_measure @parent_measure.setter def parent_measure(self, val): if not val and not isinstance_as_string(val, "Measure"): raise TypeError self._parent_measure = val @property def signatures(self) -> List[int]: """ Set and gets signatures. If :obj:`~parent_measure` exists, beats inside its voices will be updated. If it is set to ``None`` a 4/4 signature will be used. :return: A list of int representing time signature. """ return self._signatures @signatures.setter def signatures(self, val): if not val: val = [4, 4] for v in val: if not isinstance(v, int) and not isinstance(v, str): raise TypeError self._signatures = val self._reset_actual_signatures() self._update_signature_objects() if self.parent_measure: self.parent_measure._update_voice_beats() @property def show(self) -> bool: """ If time signature is shown or not. :type: bool :return: bool """ return self._show @show.setter def show(self, val): if not isinstance(val, bool): raise TypeError self._show = val
[docs] def get_beats_quarter_durations(self) -> List[QuarterDuration]: """ :return: List of quarter durations according to :obj:`actual_signatures` """ return [QuarterDuration(numerator, denominator) * 4 for numerator, denominator in [self.actual_signatures[i:i + 2] for i in range(0, len(self.actual_signatures), 2)]]
def __copy__(self): cp = self.__class__(*self.signatures, show=self.show) cp._actual_signatures = self._actual_signatures return cp def __rmul__(self, other): return [self.__copy__() for _ in range(other)]
[docs]def flatten_times(times) -> List[Time]: """ :param times: an expandable list of times or tuples representing times. For example [x * Time(3, 8)] return x time intances of Time(3, 8) :return: List[Time] >>> ts = [2 * Time(3, 8), (3, 4), 3 * [(1, 8)], Time(1, 8, 3, 4), Time(3, 4)] >>> [t.signatures for t in flatten_times(ts)] [(3, 8), (3, 8), (3, 4), (1, 8), (1, 8), (1, 8), (1, 8, 3, 4), (3, 4)] """ output = [] for time in times: if isinstance(time, Time): output.append(time) elif hasattr(time, '__iter__'): if {isinstance(t, int) for t in time} == {True}: output.append(Time(*time)) else: for t in time: if isinstance(t, Time): output.append(t) else: output.append(Time(*t)) return output