class EventSequence(object): """ A class to collect a sequence of Event's ordered (increasing) by the Event's time value. The class contains the following event accounting structures: 1) OrderedMap: ordering the events by time in a map that provides a floor() function. 2) successor: a dict that maps events to successors. 3) predecessor: a dict that maps events to predecessors. 4) first: first event in the event sequence. 5) last: last event in the event sequence. """ def __init__(self, event_list=None): """ Constructor. Args: event_list: Any of None, a single Event, or a list of Events. """ self.ordered_map = OrderedMap() self._successor = {} self._predecessor = {} self.__first = None self.__last = None if event_list: self.add(event_list) @property def sequence_list(self): return list(self.ordered_map.get(x) for x in self.ordered_map.keys()) @property def is_empty(self): return self.ordered_map.is_empty() def floor(self, time): return self.ordered_map.floor(time) def event(self, index): return self.ordered_map.get(index) def floor_event(self, time): floor_position = self.floor(time) return self.event(floor_position) if floor_position else None @property def first(self): return self.__first @property def last(self): return self.__last def add(self, new_members): """ Add any of a single Event or a list of Events. Args: new_members: Any of a single Event or a list of events """ if isinstance(new_members, list): mem_set = new_members inputt = [(e.time, e) for e in new_members] else: mem_set = [new_members] inputt = [(new_members.time, new_members)] for m in mem_set: if self.ordered_map.has_reverse(m): raise Exception('{0} already a member of sequence.'.format(m)) if not isinstance(m, Event): raise Exception('{0} is not an event.'.format(m)) for i in inputt: if i[1].time not in self.ordered_map: self._add_successor_predecessor_maps(i[1]) else: self._update_successor_predecessor_maps(i[1]) self.ordered_map.insert(i[0], i[1]) def remove(self, members): """ Remove any of a single Event or a list of Events already in the sequence. Args: members: Any of a single Event or a list of Events already in the sequence. """ if isinstance(members, list): for member in members: self.remove(member) else: if not self.ordered_map.has_reverse(members): raise Exception('{0} not a member of sequence'.format(members)) self._remove_successor_predecessor_maps(members) self.ordered_map.remove_key(self.ordered_map.reverse_get(members)) def move_event(self, event, new_time): """ Method to move event in sequence to a new time. Args: event: (Event) to move new_time: the new time setting for the event """ if self.event(event.time) != event: raise Exception('Given event at time {0} not in sequence'.format(event.time)) self.remove(event) event.time = new_time self.add(event) def _add_successor_predecessor_maps(self, event): fl_key = self.floor(event.time) if fl_key: a = self.event(fl_key) b = self._successor[a] # could be None event is between a and b self._successor[a] = event self._successor[event] = b self._predecessor[event] = a if b: self._predecessor[b] = event else: self.__last = event else: # this event has to come first if self.__first: self._successor[event] = self.__first self._predecessor[self.__first] = event self._predecessor[event] = None self.__first = event else: self.__first = self.__last = event self._successor[event] = None self._predecessor[event] = None def _update_successor_predecessor_maps(self, event): e = self.event(event.time) self.remove(e) self._add_successor_predecessor_maps(event) pass def _remove_successor_predecessor_maps(self, event): a = self._predecessor[event] b = self._successor[event] del self._successor[event] del self._predecessor[event] if a: self._successor[a] = b else: self.__first = b if b: self._predecessor[b] = a else: self.__last = a def clear(self): self.ordered_map.clear() self._successor.clear() self._predecessor.clear() def successor(self, event): return self._successor[event] if event in self._successor else None def predecessor(self, event): return self._predecessor[event] if event in self._predecessor else None def __str__(self): return ', '.join(str(x) for x in self.sequence_list) def print_maps(self): print('---------') if self.__first: print('first={0}'.format(self.__first)) else: print('first=None') if self.__first: print('last={0}'.format(self.__last)) else: print('last=None') print('Successor:') for i in self._successor.items(): print(' {0} --> {1}'.format(i[0].object if i[0] else 'None', i[1].object if i[1] else 'None')) print('Predecessor:') for i in self._predecessor.items(): print(' {0} --> {1}'.format(i[0].object if i[0] else 'None', i[1].object if i[1] else 'None'))
class HarmonicContextTrack(object): def __init__(self): """ Constructor. """ self.ordered_map = OrderedMap() self._wnt_duration = Duration(0) def __getitem__(self, position): """ Get the harmonic context based on the floor of the given position. :param position: :return: """ floor_position = self.ordered_map.floor(position) return None if floor_position is None else self.ordered_map[ floor_position] def __len__(self): return len(self.ordered_map) @property def duration(self): return self._wnt_duration def hc_list(self): return [self.ordered_map[hc] for hc in self.ordered_map.keys()] def reset(self): self._reset_hc_list(self.ordered_map.value_items()) def append(self, harmonic_context): """ Append a harmonic context to the end of the track. It is recommended to use this call to build the track, as the key data members are not rebuilt. :param harmonic_context: :return: """ last_hc = None if self.ordered_map.is_empty( ) else self.ordered_map.value_items()[-1] harmonic_context.position = Position( 0) if last_hc is None else last_hc.position + last_hc.duration self.ordered_map.insert(harmonic_context.position, harmonic_context) self._wnt_duration += harmonic_context.duration.duration def append_first(self, harmonic_context): """ Append a harmonic context to the beginning of the track, and shove right all existing HC's by the duration of the added HC. :param harmonic_context: :return: """ hc_list = self.ordered_map.value_items() hc_list.insert(0, harmonic_context) self._reset_hc_list(hc_list) def insert(self, position_key, harmonic_context): """ Insert a harmonic context at a given position. The insertion happens after the HC that is floor(position_key). :param position_key: :param harmonic_context: :return: """ hc_list = self.ordered_map.value_items() hc_target_index = self.ordered_map.floor(position_key) index = 0 if hc_target_index is None else hc_list.index( self.ordered_map[hc_target_index]) hc_list.insert(index, harmonic_context) harmonic_context.position = position_key self._reset_hc_list(hc_list) def get_hc_by_position(self, position): return self.ordered_map[self.ordered_map.floor(position)] def replace(self, position_key, harmonic_context): if position_key not in self.ordered_map: raise Exception( 'Attempt to replace a harmonic context with invalid key') harmonic_context.position = position_key self.ordered_map.insert(position_key, harmonic_context) self._reset_hc_list(self.ordered_map.value_items()) def remove(self, harmonic_context): """ Remove the given harmonic content. Error if it does not exist. :param harmonic_context: :return: """ if harmonic_context.position not in self.ordered_map or \ self.ordered_map[harmonic_context.position] != harmonic_context: raise Exception( 'Attempt of remove harmonic context that is not in list') self.ordered_map.remove_key(harmonic_context.position) self._reset_hc_list(self.ordered_map.value_items()) def _reset_hc_list(self, hc_list): p = Position(0) new_ordered_map = OrderedMap() for hc in hc_list: hc.position = p new_ordered_map.insert(p, hc) p += hc.duration self.ordered_map = new_ordered_map self._wnt_duration = Duration(p.position) def clear(self): self.ordered_map = OrderedMap() self._wnt_duration = Duration(0) def __str__(self): l = self.hc_list() return '\n'.join('[{0}] {1}'.format(i, str(l[i])) for i in range(0, len(self))) def sub_hct(self, sub_track_interval=None): """ Take a sub_track of this hct. :param sub_track_interval: NmericInterval. If none, the entire hct. :return: """ sub_track_interval = NumericInterval(Fraction(0), self.duration.duration) if sub_track_interval is None else \ sub_track_interval new_track = HarmonicContextTrack() for hc in self.hc_list(): hc_interval = NumericInterval( hc.position.position, hc.position.position + hc.duration.duration) hc_intersect = hc_interval.intersection(sub_track_interval) if hc_intersect is not None: new_hc = HarmonicContext(hc.tonality, hc.chord, Duration(hc_intersect.length()), Position(hc_intersect.lower)) new_track.append(new_hc) return new_track def reverse(self): new_track = HarmonicContextTrack() offset = Position(0) for hc in reversed(self.hc_list()): hs_prime = HarmonicContext(hc.tonality, hc.chord, hc.duration, offset) new_track.append(hs_prime) offset = offset + hc.duration return new_track