Source code for evans.consort.LogicalTieCollection

import abjad

from .AbjadObject import AbjadObject


[docs]class LogicalTieCollection(AbjadObject): r""" A mutable always-sorted collection of logical_ties. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> print(abjad.storage(logical_tie_collection)) evans.LogicalTieCollection( [ abjad.LogicalTie( [ abjad.Note("c'4"), ] ), abjad.LogicalTie( [ abjad.Note("c'4"), ] ), abjad.LogicalTie( [ abjad.Note("c'4"), ] ), abjad.LogicalTie( [ abjad.Note("c'4"), ] ), ] ) """ ### CLASS VARIABLES ### __slots__ = ("_root_node",) ### INITIALIZER ### def __init__(self, logical_ties=None): self._root_node = None if logical_ties is not None and logical_ties: self.insert(logical_ties) ### SPECIAL METHODS ###
[docs] def __contains__(self, logical_tie): r""" Is true if this logical_tie collection contains `logical_tie`. Otherwise false. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> new_staff = abjad.Staff("c'4. c'8 c'4 c'4") >>> logical_ties[0] in logical_tie_collection True >>> new_staff[0] in logical_tie_collection False Returns boolean. """ assert LogicalTieCollection._is_logical_tie(logical_tie) candidates = self.find_logical_ties_starting_at( abjad.get.timespan(logical_tie).start_offset ) result = logical_tie in candidates return result
[docs] def __getitem__(self, i): r""" Gets logical_tie at index `i`. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> logical_tie_collection[-1] LogicalTie([Note("c'4")]) >>> for logical_tie in logical_tie_collection[:3]: ... logical_tie ... LogicalTie([Note("c'4")]) LogicalTie([Note("c'4")]) LogicalTie([Note("c'4")]) Returns logical_tie or logical_ties. """ def recurse_by_index(node, index): if node.node_start_index <= index < node.node_stop_index: return node.payload[index - node.node_start_index] elif node.left_child and index < node.node_start_index: return recurse_by_index(node.left_child, index) elif node.right_child and node.node_stop_index <= index: return recurse_by_index(node.right_child, index) def recurse_by_slice(node, start, stop): result = [] if node is None: return result if start < node.node_start_index and node.left_child: result.extend(recurse_by_slice(node.left_child, start, stop)) if start < node.node_stop_index and node.node_start_index < stop: node_start = start - node.node_start_index if node_start < 0: node_start = 0 node_stop = stop - node.node_start_index result.extend(node.payload[node_start:node_stop]) if node.node_stop_index <= stop and node.right_child: result.extend(recurse_by_slice(node.right_child, start, stop)) return result if isinstance(i, int): if self._root_node is None: raise IndexError if i < 0: i = self._root_node.subtree_stop_index + i if i < 0 or self._root_node.subtree_stop_index <= i: raise IndexError return recurse_by_index(self._root_node, i) elif isinstance(i, slice): if self._root_node is None: return [] indices = i.indices(self._root_node.subtree_stop_index) start, stop = indices[0], indices[1] return recurse_by_slice(self._root_node, start, stop) raise TypeError("Indices must be integers or slices, got {}".format(i))
[docs] def __iter__(self): r""" Iterates logical_ties in this logical_tie collection. .. container:: example >>> staff = abjad.Staff("c'4. c'8 c'4.. c'16") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> for logical_tie in logical_tie_collection: ... logical_tie ... LogicalTie([Note("c'4.")]) LogicalTie([Note("c'8")]) LogicalTie([Note("c'4..")]) LogicalTie([Note("c'16")]) Returns generator. """ def recurse(node): if node is not None: if node.left_child is not None: for logical_tie in recurse(node.left_child): yield logical_tie for logical_tie in node.payload: yield logical_tie if node.right_child is not None: for logical_tie in recurse(node.right_child): yield logical_tie return recurse(self._root_node)
[docs] def __len__(self): r""" Gets length of this logical_tie collection. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> len(logical_tie_collection) 4 Returns integer. """ if self._root_node is None: return 0 return self._root_node.subtree_stop_index
[docs] def __setitem__(self, i, new): r""" Sets logical_ties at index `i` to `new`. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> new_staff = abjad.Staff("c'1") >>> logical_tie_collection[:3] = new_staff[:] Returns none. """ if isinstance(i, (int, slice)): old = self[i] self.remove(old) self.insert(new) else: message = "Indices must be ints or slices, got {}".format(i) raise TypeError(message)
### PRIVATE METHODS ### def _insert_node(self, node, start_offset): from .TimespanCollectionNode import TimespanCollectionNode if node is None: return TimespanCollectionNode(start_offset) if start_offset < node.start_offset: node.left_child = self._insert_node(node.left_child, start_offset) elif node.start_offset < start_offset: node.right_child = self._insert_node(node.right_child, start_offset) return self._rebalance(node) def _insert_logical_tie(self, logical_tie): self._root_node = self._insert_node( self._root_node, abjad.get.timespan(logical_tie).start_offset ) node = self._search( self._root_node, abjad.get.timespan(logical_tie).start_offset ) node.payload.append(logical_tie) node.payload.sort(key=lambda x: abjad.get.timespan(x).stop_offset) @staticmethod def _is_logical_tie(expr): if abjad.get.timespan(expr) is not None: return True return False def _rebalance(self, node): if node is not None: if 1 < node.balance: if 0 <= node.right_child.balance: node = self._rotate_right_right(node) else: node = self._rotate_right_left(node) elif node.balance < -1: if node.left_child.balance <= 0: node = self._rotate_left_left(node) else: node = self._rotate_left_right(node) assert -1 <= node.balance <= 1 return node def _remove_node(self, node, start_offset): if node is not None: if node.start_offset == start_offset: if node.left_child and node.right_child: next_node = node.right_child while next_node.left_child: next_node = next_node.left_child node._start_offset = next_node._start_offset node._payload = next_node._payload node.right_child = self._remove_node( node.right_child, next_node.start_offset ) else: node = node.left_child or node.right_child elif start_offset < node.start_offset: node.left_child = self._remove_node(node.left_child, start_offset) elif node.start_offset < start_offset: node.right_child = self._remove_node(node.right_child, start_offset) return self._rebalance(node) def _remove_logical_tie(self, logical_tie, old_start_offset=None): start_offset = abjad.get.timespan(logical_tie).start_offset if old_start_offset is not None: start_offset = old_start_offset node = self._search(self._root_node, start_offset) if node is None: return if logical_tie in node.payload: node.payload.remove(logical_tie) if not node.payload: self._root_node = self._remove_node(self._root_node, start_offset) if isinstance(logical_tie, LogicalTieCollection): logical_tie._parents.remove(self) def _rotate_left_left(self, node): next_node = node.left_child node.left_child = next_node.right_child next_node.right_child = node return next_node def _rotate_left_right(self, node): node.left_child = self._rotate_right_right(node.left_child) next_node = self._rotate_left_left(node) return next_node def _rotate_right_left(self, node): node.right_child = self._rotate_left_left(node.right_child) next_node = self._rotate_right_right(node) return next_node def _rotate_right_right(self, node): next_node = node.right_child node.right_child = next_node.left_child next_node.left_child = node return next_node def _search(self, node, start_offset): if node is not None: if node.start_offset == start_offset: return node elif node.left_child and start_offset < node.start_offset: return self._search(node.left_child, start_offset) elif node.right_child and node.start_offset < start_offset: return self._search(node.right_child, start_offset) return None def _update_indices(self, node): def recurse(node, parent_stop_index=None): if node is None: return if node.left_child is not None: recurse(node.left_child, parent_stop_index=parent_stop_index) node._node_start_index = node.left_child.subtree_stop_index node._subtree_start_index = node.left_child.subtree_start_index elif parent_stop_index is None: node._node_start_index = 0 node._subtree_start_index = 0 else: node._node_start_index = parent_stop_index node._subtree_start_index = parent_stop_index node._node_stop_index = node.node_start_index + len(node.payload) node._subtree_stop_index = node.node_stop_index if node.right_child is not None: recurse(node.right_child, parent_stop_index=node.node_stop_index) node._subtree_stop_index = node.right_child.subtree_stop_index recurse(node) def _update_offsets(self, node): if node is None: return stop_offset_low = min(abjad.get.timespan(x).stop_offset for x in node.payload) stop_offset_high = max(abjad.get.timespan(x).stop_offset for x in node.payload) if node.left_child: left_child = self._update_offsets(node.left_child) if left_child.stop_offset_low < stop_offset_low: stop_offset_low = left_child.stop_offset_low if stop_offset_high < left_child.stop_offset_high: stop_offset_high = left_child.stop_offset_high if node.right_child: right_child = self._update_offsets(node.right_child) if right_child.stop_offset_low < stop_offset_low: stop_offset_low = right_child.stop_offset_low if stop_offset_high < right_child.stop_offset_high: stop_offset_high = right_child.stop_offset_high node._stop_offset_low = stop_offset_low node._stop_offset_high = stop_offset_high return node def _get_format_specification(self): values = [] logical_ties = [x for x in self] if logical_ties: values.append(logical_ties) names = [] return abjad.FormatSpecification( client=self, storage_format_args_values=values, storage_format_keyword_names=names, ) ### PUBLIC METHODS ###
[docs] def find_logical_ties_starting_at(self, offset): results = [] node = self._search(self._root_node, offset) if node is not None: results.extend(node.payload) return tuple(results)
[docs] def find_logical_ties_stopping_at(self, offset): def recurse(node, offset): result = [] if node is not None: if node.stop_offset_low <= offset <= node.stop_offset_high: for logical_tie in node.payload: if abjad.get.timespan(logical_tie).stop_offset == offset: result.append(logical_tie) if node.left_child is not None: result.extend(recurse(node.left_child, offset)) if node.right_child is not None: result.extend(recurse(node.right_child, offset)) return result results = recurse(self._root_node, offset) results.sort( key=lambda x: ( abjad.get.timespan(x).start_offset, abjad.get.timespan(x).stop_offset, ) ) return tuple(results)
[docs] def find_logical_ties_overlapping_offset(self, offset): r""" Finds logical_ties overlapping `offset`. .. container:: example >>> staff = abjad.Staff("c'4 c'2 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> for x in logical_tie_collection.find_logical_ties_overlapping_offset(0.5): ... x ... LogicalTie([Note("c'2")]) Returns tuple of 0 or more logical_ties. """ def recurse(node, offset, indent=0): result = [] if node is not None: if node.start_offset < offset < node.stop_offset_high: result.extend(recurse(node.left_child, offset, indent + 1)) for logical_tie in node.payload: if offset < abjad.get.timespan(logical_tie).stop_offset: result.append(logical_tie) result.extend(recurse(node.right_child, offset, indent + 1)) elif offset <= node.start_offset: result.extend(recurse(node.left_child, offset, indent + 1)) return result results = recurse(self._root_node, offset) results.sort( key=lambda x: ( abjad.get.timespan(x).start_offset, abjad.get.timespan(x).stop_offset, ) ) return tuple(results)
[docs] def find_logical_ties_intersecting_timespan(self, timespan): r""" Finds logical_ties overlapping `timespan`. .. container:: example >>> staff = abjad.Staff("c'4 c'2 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> timespan = abjad.Timespan((3, 8), 1) >>> for x in logical_tie_collection.find_logical_ties_intersecting_timespan(timespan): ... x ... LogicalTie([Note("c'2")]) LogicalTie([Note("c'4")]) Returns tuple of 0 or more logical_ties. """ def recurse(node, timespan): result = [] if node is not None: if timespan.intersects_timespan(node): result.extend(recurse(node.left_child, timespan)) for candidate_logical_tie in node.payload: if abjad.get.timespan( candidate_logical_tie ).intersects_timespan(timespan): result.append(candidate_logical_tie) result.extend(recurse(node.right_child, timespan)) elif (timespan.start_offset <= node.start_offset) or ( timespan.stop_offset <= node.start_offset ): result.extend(recurse(node.left_child, timespan)) return result results = recurse(self._root_node, timespan) results.sort( key=lambda x: ( abjad.get.timespan(x).start_offset, abjad.get.timespan(x).stop_offset, ) ) return tuple(results)
# new from greg...spotty when to check node vs timespan? What even is in the node?
[docs] def find_logical_ties_starting_during_timespan(self, timespan): # still wrong... r = self.find_logical_ties_intersecting_timespan(timespan) return tuple( _ for _ in r if abjad.get.timespan(_).starts_during_timespan(timespan) )
[docs] def get_start_offset_after(self, offset): r""" Gets start offst in this logical_tie collection after `offset`. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> logical_tie_collection.get_start_offset_after(-1) Offset((0, 1)) >>> logical_tie_collection.get_start_offset_after(0) Offset((1, 4)) >>> logical_tie_collection.get_start_offset_after(Offset(1, 2)) Offset((3, 4)) >>> logical_tie_collection.get_start_offset_after(6) is None True """ def recurse(node, offset): if node is None: return None result = None if node.start_offset <= offset and node.right_child: result = recurse(node.right_child, offset) elif offset < node.start_offset: result = recurse(node.left_child, offset) or node return result result = recurse(self._root_node, offset) if result is None: return None return result.start_offset
[docs] def get_start_offset_before(self, offset): r""" Gets start offst in this logical_tie collection before `offset`. .. container:: example >>> staff = abjad.Staff("c'4 c'4 c'4 c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> logical_tie_collection.get_start_offset_before(7) Offset((3, 4)) >>> logical_tie_collection.get_start_offset_before(Offset(1, 2)) Offset((1, 4)) >>> logical_tie_collection.get_start_offset_before(Offset(1, 4)) Offset((0, 1)) >>> logical_tie_collection.get_start_offset_before(0) is None True """ def recurse(node, offset): if node is None: return None result = None if node.start_offset < offset: result = recurse(node.right_child, offset) or node elif offset <= node.start_offset and node.left_child: result = recurse(node.left_child, offset) return result result = recurse(self._root_node, offset) if result is None: return None return result.start_offset
[docs] def index(self, logical_tie): assert self._is_logical_tie(logical_tie) node = self._search( self._root_node, abjad.get.timespan(logical_tie).start_offset ) if node is None or logical_tie not in node.payload: raise ValueError("{} not in logical_tie collection.".format(logical_tie)) index = node.payload.index(logical_tie) + node.node_start_index return index
[docs] def insert(self, logical_ties): r""" Inserts `logical_ties` into this logical_tie collection. .. container:: example >>> staff = abjad.Staff("c'2 c'2") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> for x in logical_tie_collection: ... x ... LogicalTie([Note("c'2")]) LogicalTie([Note("c'2")]) `logical_ties` may be a single logical_tie or an iterable of logical_ties. Returns none. """ if self._is_logical_tie(logical_ties): logical_ties = [logical_ties] for logical_tie in logical_ties: if not self._is_logical_tie(logical_tie): continue self._insert_logical_tie(logical_tie) self._update_indices(self._root_node) self._update_offsets(self._root_node)
[docs] def remove(self, logical_ties): r""" Removes logical_ties from this logical_tie collection. .. container:: example >>> staff = abjad.Staff("c'2. c'4") >>> logical_ties = abjad.select(staff).logical_ties() >>> logical_tie_collection = evans.LogicalTieCollection() >>> for tie in logical_ties: ... logical_tie_collection.insert(tie) ... >>> logical_tie_collection.remove(logical_ties[0]) >>> for logical_tie in logical_tie_collection: ... logical_tie ... LogicalTie([Note("c'4")]) """ if self._is_logical_tie(logical_ties): logical_ties = [logical_ties] for logical_tie in logical_ties: if not self._is_logical_tie(logical_tie): continue self._remove_logical_tie(logical_tie) self._update_indices(self._root_node) self._update_offsets(self._root_node)
### PUBLIC PROPERTIES ### @property def all_offsets(self): offsets = set() for logical_tie in self: offsets.add(abjad.get.timespan(logical_tie).start_offset) offsets.add(abjad.get.timespan(logical_tie).stop_offset) return tuple(sorted(offsets)) @property def all_start_offsets(self): start_offsets = set() for logical_tie in self: start_offsets.add(abjad.get.timespan(logical_tie).start_offset) return tuple(sorted(start_offsets)) @property def all_stop_offsets(self): stop_offsets = set() for logical_tie in self: stop_offsets.add(abjad.get.timespan(logical_tie).stop_offset) return tuple(sorted(stop_offsets)) @property def earliest_start_offset(self): def recurse(node): if node.left_child is not None: return recurse(node.left_child) return node.start_offset if self._root_node is not None: return recurse(self._root_node) return float("-inf") @property def earliest_stop_offset(self): if self._root_node is not None: return self._root_node.stop_offset_low return float("inf") @property def latest_start_offset(self): def recurse(node): if node.right_child is not None: return recurse(node._right_child) return node.start_offset if self._root_node is not None: return recurse(self._root_node) return float("-inf") @property def latest_stop_offset(self): if self._root_node is not None: return self._root_node.stop_offset_high return float("inf") @property def start_offset(self): return self.earliest_start_offset @property def stop_offset(self): return self.latest_stop_offset