1# -*- coding: utf-8 -*- 2# ----------------------------------------------------------------------------- 3# Name: sorting.py 4# Purpose: Music21 class for sorting 5# 6# Authors: Michael Scott Cuthbert 7# 8# Copyright: Copyright © 2014-2015 Michael Scott Cuthbert and the music21 9# Project 10# License: BSD, see license.txt 11# ----------------------------------------------------------------------------- 12''' 13This module defines a single class, SortTuple, which is a named tuple that can 14sort against bare offsets and other SortTuples. 15 16This is a performance-critical object. 17 18It also defines three singleton instance of the SortTupleLow class as ZeroSortTupleDefault, 19ZeroSortTupleLow and 20ZeroSortTupleHigh which are sortTuple at 21offset 0.0, priority [0, -inf, inf] respectively: 22 23>>> sorting.ZeroSortTupleDefault 24SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=0, isNotGrace=1, insertIndex=0) 25>>> sorting.ZeroSortTupleLow 26SortTuple(atEnd=0, offset=0.0, priority=-inf, classSortOrder=0, isNotGrace=1, insertIndex=0) 27>>> sorting.ZeroSortTupleHigh 28SortTuple(atEnd=0, offset=0.0, priority=inf, classSortOrder=0, isNotGrace=1, insertIndex=0) 29''' 30from collections import namedtuple 31from math import inf as INFINITY 32from music21 import exceptions21 33 34_attrList = ['atEnd', 'offset', 'priority', 'classSortOrder', 'isNotGrace', 'insertIndex'] 35 36 37class SortingException(exceptions21.Music21Exception): 38 pass 39 40 41class SortTuple(namedtuple('SortTuple', _attrList)): 42 ''' 43 Derived class of namedTuple which allows for comparisons with pure ints/fractions. 44 45 >>> n = note.Note() 46 >>> s = stream.Stream() 47 >>> s.insert(4, n) 48 >>> st = n.sortTuple() 49 >>> st 50 SortTuple(atEnd=0, offset=4.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=...) 51 >>> st.shortRepr() 52 '4.0 <0.20...>' 53 >>> st.atEnd 54 0 55 >>> st.offset 56 4.0 57 58 >>> st < 5.0 59 True 60 >>> 5.0 > st 61 True 62 >>> st > 3.0 63 True 64 >>> 3.0 < st 65 True 66 67 >>> st == 4.0 68 True 69 70 >>> ts = bar.Barline('double') 71 >>> t = stream.Stream() 72 >>> t.storeAtEnd(ts) 73 >>> ts_st = ts.sortTuple() 74 >>> ts_st 75 SortTuple(atEnd=1, offset=0.0, priority=0, classSortOrder=-5, isNotGrace=1, insertIndex=...) 76 >>> st < ts_st 77 True 78 >>> ts_st > 999999 79 True 80 >>> import math 81 >>> ts_st == math.inf 82 True 83 84 Construct one w/ keywords: 85 86 >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, 87 ... isNotGrace=1, insertIndex=323) 88 >>> st.shortRepr() 89 '1.0 <0.20.323>' 90 91 or as tuple: 92 93 >>> st = sorting.SortTuple(0, 1.0, 0, 20, 1, 323) 94 >>> st.shortRepr() 95 '1.0 <0.20.323>' 96 97 ''' 98 def __new__(cls, *tupEls, **kw): 99 # noinspection PyTypeChecker 100 return super(SortTuple, cls).__new__(cls, *tupEls, **kw) 101 102 def __eq__(self, other): 103 if isinstance(other, tuple): 104 return super().__eq__(other) 105 try: 106 if self.atEnd == 1 and other != INFINITY: 107 return False 108 elif self.atEnd == 1: 109 return True 110 else: 111 return self.offset == other 112 except ValueError: 113 return NotImplemented 114 115 def __lt__(self, other): 116 if isinstance(other, tuple): 117 return super().__lt__(other) 118 try: 119 if self.atEnd == 1: 120 return False 121 else: 122 return self.offset < other 123 except ValueError: 124 return NotImplemented 125 126 def __gt__(self, other): 127 if isinstance(other, tuple): 128 return super().__gt__(other) 129 try: 130 if self.atEnd == 1 and other != INFINITY: 131 return True 132 elif self.atEnd == 1: 133 return False 134 else: 135 return self.offset > other 136 except ValueError: 137 return NotImplemented 138 139 def __ne__(self, other): 140 return not self.__eq__(other) 141 142 def __le__(self, other): 143 return self.__lt__(other) or self.__eq__(other) 144 145 def __ge__(self, other): 146 return self.__gt__(other) or self.__eq__(other) 147 148 def shortRepr(self): 149 ''' 150 Returns a nice representation of a SortTuple 151 152 >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, 153 ... isNotGrace=1, insertIndex=323) 154 >>> st.shortRepr() 155 '1.0 <0.20.323>' 156 157 >>> st = sorting.SortTuple(atEnd=1, offset=1.0, priority=4, classSortOrder=7, 158 ... isNotGrace=0, insertIndex=200) 159 >>> st.shortRepr() 160 'End <4.7.[Grace].200>' 161 ''' 162 reprParts = [] 163 if self.atEnd: 164 reprParts.append('End') 165 else: 166 reprParts.append(str(self.offset)) 167 reprParts.append(' <') 168 reprParts.append(str(self.priority)) 169 reprParts.append('.') 170 reprParts.append(str(self.classSortOrder)) 171 172 if self.isNotGrace == 0: 173 reprParts.append('.[Grace]') 174 reprParts.append('.') 175 reprParts.append(str(self.insertIndex)) 176 reprParts.append('>') 177 return ''.join(reprParts) 178 179 def modify(self, **kw): 180 ''' 181 return a new SortTuple identical to the previous, except with 182 the given keyword modified. Works only with keywords. 183 184 >>> st = sorting.SortTuple(atEnd=0, offset=1.0, priority=0, classSortOrder=20, 185 ... isNotGrace=1, insertIndex=32) 186 >>> st2 = st.modify(offset=2.0) 187 >>> st2.shortRepr() 188 '2.0 <0.20.32>' 189 >>> st2 190 SortTuple(atEnd=0, offset=2.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=32) 191 192 >>> st3 = st2.modify(atEnd=1, isNotGrace=0) 193 >>> st3.shortRepr() 194 'End <0.20.[Grace].32>' 195 196 The original tuple is never modified (hence tuple): 197 198 >>> st.offset 199 1.0 200 201 Changing offset, but nothing else, helps in creating .flatten() positions. 202 ''' 203 outList = [kw.get(attr, getattr(self, attr)) for attr in _attrList] 204 return self.__class__(*outList) 205 206 def add(self, other): 207 ''' 208 Add all attributes from one sortTuple to another, 209 returning a new one. 210 211 >>> n = note.Note() 212 >>> n.offset = 10 213 >>> s = stream.Stream() 214 >>> s.offset = 10 215 >>> n.sortTuple() 216 SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=0) 217 >>> s.sortTuple() 218 SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=-20, isNotGrace=1, insertIndex=0) 219 >>> s.sortTuple().add(n.sortTuple()) 220 SortTuple(atEnd=0, offset=20.0, priority=0, classSortOrder=0, isNotGrace=1, insertIndex=0) 221 222 Note that atEnd and isNotGrace are equal to other's value. are upper bounded at 1 and 223 take the maxValue of either. 224 ''' 225 if not isinstance(other, self.__class__): 226 raise SortingException('Cannot add attributes from a different class') 227 228 outList = [max(getattr(self, attr), getattr(other, attr)) 229 if attr in ('atEnd', 'isNotGrace') 230 else (getattr(self, attr) + getattr(other, attr)) 231 for attr in _attrList] 232 233 return self.__class__(*outList) 234 235 def sub(self, other): 236 ''' 237 Subtract all attributes from to another. atEnd and isNotGrace take the min value of either. 238 239 >>> n = note.Note() 240 >>> n.offset = 10 241 >>> s = stream.Stream() 242 >>> s.offset = 10 243 >>> n.sortTuple() 244 SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=20, isNotGrace=1, insertIndex=0) 245 >>> s.sortTuple() 246 SortTuple(atEnd=0, offset=10.0, priority=0, classSortOrder=-20, isNotGrace=1, insertIndex=0) 247 >>> s.sortTuple().sub(n.sortTuple()) 248 SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=-40, isNotGrace=1, insertIndex=0) 249 250 Note that atEnd and isNotGrace are lower bounded at 0. 251 ''' 252 if not isinstance(other, self.__class__): 253 raise SortingException('Cannot add attributes from a different class') 254 255 outList = [min(getattr(self, attr), getattr(other, attr)) 256 if attr in ('atEnd', 'isNotGrace') 257 else (getattr(self, attr) - getattr(other, attr)) 258 for attr in _attrList] 259 260 return self.__class__(*outList) 261 262 263ZeroSortTupleDefault = SortTuple(atEnd=0, offset=0.0, priority=0, classSortOrder=0, 264 isNotGrace=1, insertIndex=0) 265 266ZeroSortTupleLow = SortTuple(atEnd=0, offset=0.0, priority=-INFINITY, classSortOrder=0, 267 isNotGrace=1, insertIndex=0) 268 269ZeroSortTupleHigh = SortTuple(atEnd=0, offset=0.0, priority=INFINITY, classSortOrder=0, 270 isNotGrace=1, insertIndex=0) 271 272 273# ----------------------------------------------------------------------------- 274if __name__ == '__main__': 275 import music21 276 music21.mainTest() 277