# Copyright 2023 The Magenta Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Abstract base classes for working with musical event sequences. The abstract `EventSequence` class is an interface for a sequence of musical events. The `SimpleEventSequence` class is a basic implementation of this interface. """ import abc import copy from note_seq import constants DEFAULT_STEPS_PER_BAR = constants.DEFAULT_STEPS_PER_BAR DEFAULT_STEPS_PER_QUARTER = constants.DEFAULT_STEPS_PER_QUARTER STANDARD_PPQ = constants.STANDARD_PPQ class NonIntegerStepsPerBarError(Exception): pass class EventSequence(object): """Stores a quantized stream of events. EventSequence is an abstract class to use as an interface for interacting with musical event sequences. Concrete implementations SimpleEventSequence (and its descendants Melody and ChordProgression) and LeadSheet represent sequences of musical events of particular types. In all cases, model-specific code is responsible for converting this representation to SequenceExample protos for TensorFlow. EventSequence represents an iterable object. Simply iterate to retrieve the events. Attributes: start_step: The offset of the first step of the sequence relative to the beginning of the source sequence. end_step: The offset to the beginning of the bar following the last step of the sequence relative to the beginning of the source sequence. steps: A Python list containing the time step at each event of the sequence. """ __metaclass__ = abc.ABCMeta @property @abc.abstractmethod def start_step(self): pass @property @abc.abstractmethod def end_step(self): pass @property @abc.abstractmethod def steps(self): pass @abc.abstractmethod def append(self, event): """Appends event to the end of the sequence. Args: event: The event to append to the end. """ pass @abc.abstractmethod def set_length(self, steps, from_left=False): """Sets the length of the sequence to the specified number of steps. If the event sequence is not long enough, will pad to make the sequence the specified length. If it is too long, it will be truncated to the requested length. Args: steps: How many steps long the event sequence should be. from_left: Whether to add/remove from the left instead of right. """ pass @abc.abstractmethod def __getitem__(self, i): """Returns the event at the given index.""" pass @abc.abstractmethod def __iter__(self): """Returns an iterator over the events.""" pass @abc.abstractmethod def __len__(self): """How many events are in this EventSequence. Returns: Number of events as an integer. """ pass class SimpleEventSequence(EventSequence): """Stores a quantized stream of events. This class can be instantiated, but its main purpose is to serve as a base class for Melody, ChordProgression, and any other simple stream of musical events. SimpleEventSequence represents an iterable object. Simply iterate to retrieve the events. Attributes: start_step: The offset of the first step of the sequence relative to the beginning of the source sequence. Should always be the first step of a bar. end_step: The offset to the beginning of the bar following the last step of the sequence relative to the beginning of the source sequence. Will always be the first step of a bar. steps_per_quarter: Number of steps in in a quarter note. steps_per_bar: Number of steps in a bar (measure) of music. """ def __init__(self, pad_event, events=None, start_step=0, steps_per_bar=DEFAULT_STEPS_PER_BAR, steps_per_quarter=DEFAULT_STEPS_PER_QUARTER): """Construct a SimpleEventSequence. If `events` is specified, instantiate with the provided event list. Otherwise, create an empty SimpleEventSequence. Args: pad_event: Event value to use when padding sequences. events: List of events to instantiate with. start_step: The integer starting step offset. steps_per_bar: The number of steps in a bar. steps_per_quarter: The number of steps in a quarter note. """ self._pad_event = pad_event if events is not None: self._from_event_list(events, start_step=start_step, steps_per_bar=steps_per_bar, steps_per_quarter=steps_per_quarter) else: self._events = [] self._steps_per_bar = steps_per_bar self._steps_per_quarter = steps_per_quarter self._start_step = start_step self._end_step = start_step def _reset(self): """Clear events and reset object state.""" self._events = [] self._steps_per_bar = DEFAULT_STEPS_PER_BAR self._steps_per_quarter = DEFAULT_STEPS_PER_QUARTER self._start_step = 0 self._end_step = 0 def _from_event_list(self, events, start_step=0, steps_per_bar=DEFAULT_STEPS_PER_BAR, steps_per_quarter=DEFAULT_STEPS_PER_QUARTER): """Initializes with a list of event values and sets attributes.""" self._events = list(events) self._start_step = start_step self._end_step = start_step + len(self) self._steps_per_bar = steps_per_bar self._steps_per_quarter = steps_per_quarter def __iter__(self): """Return an iterator over the events in this SimpleEventSequence. Returns: Python iterator over events. """ return iter(self._events) def __getitem__(self, key): """Returns the slice or individual item.""" if isinstance(key, int): return self._events[key] elif isinstance(key, slice): events = self._events.__getitem__(key) return type(self)(pad_event=self._pad_event, events=events, start_step=self.start_step + (key.start or 0), steps_per_bar=self.steps_per_bar, steps_per_quarter=self.steps_per_quarter) def __len__(self): """How many events are in this SimpleEventSequence. Returns: Number of events as an integer. """ return len(self._events) def __deepcopy__(self, memo=None): return type(self)(pad_event=self._pad_event, events=copy.deepcopy(self._events, memo), start_step=self.start_step, steps_per_bar=self.steps_per_bar, steps_per_quarter=self.steps_per_quarter) def __eq__(self, other): if type(self) is not type(other): return False return (list(self) == list(other) and self.steps_per_bar == other.steps_per_bar and self.steps_per_quarter == other.steps_per_quarter and self.start_step == other.start_step and self.end_step == other.end_step) @property def start_step(self): return self._start_step @property def end_step(self): return self._end_step @property def steps(self): return list(range(self._start_step, self._end_step)) @property def steps_per_bar(self): return self._steps_per_bar @property def steps_per_quarter(self): return self._steps_per_quarter def append(self, event): """Appends event to the end of the sequence and increments the end step. Args: event: The event to append to the end. """ self._events.append(event) self._end_step += 1 def set_length(self, steps, from_left=False): """Sets the length of the sequence to the specified number of steps. If the event sequence is not long enough, pads to make the sequence the specified length. If it is too long, it will be truncated to the requested length. Args: steps: How many steps long the event sequence should be. from_left: Whether to add/remove from the left instead of right. """ if steps > len(self): if from_left: self._events[:0] = [self._pad_event] * (steps - len(self)) else: self._events.extend([self._pad_event] * (steps - len(self))) else: if from_left: del self._events[0:-steps] else: del self._events[steps:] if from_left: self._start_step = self._end_step - steps else: self._end_step = self._start_step + steps def increase_resolution(self, k, fill_event=None): """Increase the resolution of an event sequence. Increases the resolution of a SimpleEventSequence object by a factor of `k`. Args: k: An integer, the factor by which to increase the resolution of the event sequence. fill_event: Event value to use to extend each low-resolution event. If None, each low-resolution event value will be repeated `k` times. """ if fill_event is None: fill = lambda event: [event] * k else: fill = lambda event: [event] + [fill_event] * (k - 1) new_events = [] for event in self._events: new_events += fill(event) self._events = new_events self._start_step *= k self._end_step *= k self._steps_per_bar *= k self._steps_per_quarter *= k