Source code for music21.sorting

# -----------------------------------------------------------------------------
# Name:         sorting.py
# Purpose:      Music21 class for sorting
#
# Authors:      Michael Scott Asato Cuthbert
#
# Copyright:    Copyright © 2014-2015 Michael Scott Asato Cuthbert
# License:      BSD, see license.txt
# -----------------------------------------------------------------------------
'''
This module defines a single class, SortTuple, which is a named tuple that can
sort against bare offsets and other SortTuples.

This is a performance-critical object.

It also defines three singleton instances of the SortTupleLow class as ZeroSortTupleDefault,
ZeroSortTupleLow and
ZeroSortTupleHigh which are sortTuple at
offset 0.0, priority [0, -inf, inf] respectively:

>>> sorting.ZeroSortTupleDefault
SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=0, isNotGrace=1, insertIndex=0)
>>> sorting.ZeroSortTupleLow
SortTuple(atEnd=0, offset=0.0, priority=-inf, classSortOrder=0, isNotGrace=1, insertIndex=0)
>>> sorting.ZeroSortTupleHigh
SortTuple(atEnd=0, offset=0.0, priority=inf, classSortOrder=0, isNotGrace=1, insertIndex=0)
'''
from __future__ import annotations

from math import inf as INFINITY
import typing as t

from music21.common.types import OffsetQL
from music21 import exceptions21

class SortingException(exceptions21.Music21Exception):
    pass


_SortTupleBase = t.NamedTuple('SortTuple', [   # type: ignore[name-match]
    ('atEnd', int),
    ('offset', OffsetQL),
    ('priority', int),
    ('classSortOrder', int),
    ('isNotGrace', int),
    ('insertIndex', int),
])

[docs] class SortTuple(_SortTupleBase): ''' Derived class of namedTuple which allows for comparisons with pure ints/fractions. >>> n = note.Note() >>> s = stream.Stream() >>> s.insert(4, n) >>> st = n.sortTuple() >>> st SortTuple(atEnd=0, offset=4.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=...) >>> st.shortRepr() '4.0 <0.20...>' >>> st.atEnd 0 >>> st.offset 4.0 >>> st < 5.0 True >>> 5.0 > st True >>> st > 3.0 True >>> 3.0 < st True >>> st == 4.0 True >>> ts = bar.Barline('double') >>> s2 = stream.Stream() >>> s2.storeAtEnd(ts) >>> ts_st = ts.sortTuple() >>> ts_st SortTuple(atEnd=1, offset=0.0, priority=0, classSortOrder=-5, isNotGrace=1, insertIndex=...) >>> st < ts_st True >>> ts_st > 999999 True >>> import math >>> ts_st == math.inf True Construct one w/ keywords: >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, ... isNotGrace=1, insertIndex=323) >>> st.shortRepr() '1.0 <0.20.323>' or as tuple: >>> st = sorting.SortTuple(0, 1.0, 0, 20, 1, 323) >>> st.shortRepr() '1.0 <0.20.323>' ''' def __new__(cls, *tupEls, **keywords): # noinspection PyTypeChecker return super(SortTuple, cls).__new__(cls, *tupEls, **keywords)
[docs] def __eq__(self, other): if isinstance(other, tuple): return super().__eq__(other) try: if self.atEnd == 1 and other != INFINITY: return False elif self.atEnd == 1: return True else: return self.offset == other except ValueError: return NotImplemented
def __lt__(self, other): if isinstance(other, tuple): return super().__lt__(other) try: if self.atEnd == 1: return False else: return self.offset < other except ValueError: return NotImplemented def __gt__(self, other): if isinstance(other, tuple): return super().__gt__(other) try: if self.atEnd == 1 and other != INFINITY: return True elif self.atEnd == 1: return False else: return self.offset > other except ValueError: return NotImplemented def __ne__(self, other): return not self.__eq__(other) def __le__(self, other): return self.__lt__(other) or self.__eq__(other) def __ge__(self, other): return self.__gt__(other) or self.__eq__(other)
[docs] def shortRepr(self): ''' Returns a nice representation of a SortTuple. >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, ... isNotGrace=1, insertIndex=323) >>> st.shortRepr() '1.0 <0.20.323>' >>> st = sorting.SortTuple(atEnd=1, offset=1.0, priority=4, classSortOrder=7, ... isNotGrace=0, insertIndex=200) >>> st.shortRepr() 'End <4.7.[Grace].200>' ''' reprParts = [] if self.atEnd: reprParts.append('End') else: reprParts.append(str(self.offset)) reprParts.append(' <') reprParts.append(str(self.priority)) reprParts.append('.') reprParts.append(str(self.classSortOrder)) if self.isNotGrace == 0: reprParts.append('.[Grace]') reprParts.append('.') reprParts.append(str(self.insertIndex)) reprParts.append('>') return ''.join(reprParts)
[docs] def modify(self, **keywords): ''' Return a new SortTuple identical to the previous, except with the given keyword modified. Works only with keywords. >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, ... isNotGrace=1, insertIndex=32) >>> st2 = st.modify(offset=2.0) >>> st2.shortRepr() '2.0 <0.20.32>' >>> st2 SortTuple(atEnd=0, offset=2.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=32) >>> st3 = st2.modify(atEnd=1, isNotGrace=0) >>> st3.shortRepr() 'End <0.20.[Grace].32>' The original tuple is never modified (hence tuple): >>> st.offset 1.0 Changing offset, but nothing else, helps in creating .flatten() positions. ''' # _fields are the namedtuple attributes outList = [keywords.get(attr, getattr(self, attr)) for attr in self._fields] return self.__class__(*outList)
[docs] def add(self, other): ''' Add all attributes from one sortTuple to another, returning a new one. >>> n = note.Note() >>> n.offset = 10 >>> s = stream.Stream() >>> s.offset = 10 >>> n.sortTuple() SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=0) >>> s.sortTuple() SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=-20, isNotGrace=1, insertIndex=0) >>> s.sortTuple().add(n.sortTuple()) SortTuple(atEnd=0, offset=20.0, priority=0, classSortOrder=0, isNotGrace=1, insertIndex=0) Note that atEnd and isNotGrace are equal to other's value. are upper bounded at 1 and take the maxValue of either. ''' if not isinstance(other, self.__class__): raise SortingException('Cannot add attributes from a different class') outList = [max(getattr(self, attr), getattr(other, attr)) if attr in ('atEnd', 'isNotGrace') else (getattr(self, attr) + getattr(other, attr)) for attr in self._fields] # _fields are the namedtuple attributes return self.__class__(*outList)
[docs] def sub(self, other): ''' Subtract all attributes from to another. atEnd and isNotGrace take the min value of either. >>> n = note.Note() >>> n.offset = 10 >>> s = stream.Stream() >>> s.offset = 10 >>> n.sortTuple() SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=0) >>> s.sortTuple() SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=-20, isNotGrace=1, insertIndex=0) >>> s.sortTuple().sub(n.sortTuple()) SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=-40, isNotGrace=1, insertIndex=0) Note that atEnd and isNotGrace are lower bounded at 0. ''' if not isinstance(other, self.__class__): raise SortingException('Cannot add attributes from a different class') outList = [min(getattr(self, attr), getattr(other, attr)) if attr in ('atEnd', 'isNotGrace') else (getattr(self, attr) - getattr(other, attr)) for attr in self._fields] # _fields are the namedtuple attributes return self.__class__(*outList)
ZeroSortTupleDefault = SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=0, isNotGrace=1, insertIndex=0) ZeroSortTupleLow = SortTuple(atEnd=0, offset=0.0, priority=-INFINITY, classSortOrder=0, isNotGrace=1, insertIndex=0) ZeroSortTupleHigh = SortTuple(atEnd=0, offset=0.0, priority=INFINITY, classSortOrder=0, isNotGrace=1, insertIndex=0) # ----------------------------------------------------------------------------- if __name__ == '__main__': import music21 music21.mainTest()