aiexperiments-ai-duet/server/magenta/music/events_lib.py
2016-11-17 07:33:16 +03:00

389 lines
12 KiB
Python

# Copyright 2016 Google Inc. All Rights Reserved.
#
# 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.
The `EventsEncoderDecoder` is an abstract class for translating between event
sequences and model data.
"""
import abc
import numpy as np
from six.moves import range # pylint: disable=redefined-builtin
from magenta.common import sequence_example_lib
from magenta.music 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 NonIntegerStepsPerBarException(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 MonophonicMelody 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. 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.
"""
__metaclass__ = abc.ABCMeta
@abc.abstractproperty
def start_step(self):
pass
@abc.abstractproperty
def end_step(self):
pass
@abc.abstractproperty
def steps_per_quarter(self):
pass
@abc.abstractproperty
def steps_per_bar(self):
pass
@abc.abstractmethod
def append_event(self, event):
"""Appends event to the end of the sequence and increments the end step.
Args:
event: The event to append to the end.
"""
pass
@abc.abstractmethod
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.
Args:
events: List of events.
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.
"""
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, pads with `pad_event` 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
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 MonophonicMelody, ChordProgression, and any other simple stream of
musical events.
"""
def __init__(self, pad_event):
"""Construct an empty SimpleEventSequence.
Args:
pad_event: Event value to use when padding sequences.
"""
self._pad_event = pad_event
self._reset()
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 __iter__(self):
"""Return an iterator over the events in this SimpleEventSequence.
Returns:
Python iterator over events.
"""
return iter(self._events)
def __getitem__(self, i):
"""Returns the event at the given index."""
return self._events[i]
def __getslice__(self, i, j):
"""Returns the events in the given slice range."""
return self._events[i:j]
def __len__(self):
"""How many events are in this SimpleEventSequence.
Returns:
Number of events as an integer.
"""
return len(self._events)
def __deepcopy__(self, unused_memo=None):
new_copy = type(self)(pad_event=self._pad_event)
new_copy.from_event_list(list(self._events),
self.start_step,
self.steps_per_bar,
self.steps_per_quarter)
return new_copy
def __eq__(self, other):
if not isinstance(other, SimpleEventSequence):
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_per_bar(self):
return self._steps_per_bar
@property
def steps_per_quarter(self):
return self._steps_per_quarter
def append_event(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 from_event_list(self, events, start_step=0,
steps_per_bar=DEFAULT_STEPS_PER_BAR,
steps_per_quarter=DEFAULT_STEPS_PER_QUARTER):
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 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
class EventsEncoderDecoder(object):
"""An abstract class for translating between events and model data.
When building your dataset, the `encode` method takes in an event sequence
and returns a SequenceExample of inputs and labels. These SequenceExamples
are fed into the model during training and evaluation.
During generation, the `get_inputs_batch` method takes in a list of the
current event sequences and returns an inputs batch which is fed into the
model to predict what the next event should be for each sequence. The
`extend_event_sequences` method takes in the list of event sequences and the
softmax returned by the model and extends each sequence by one step by
sampling from the softmax probabilities. This loop (`get_inputs_batch` ->
inputs batch is fed through the model to get a softmax ->
`extend_event_sequences`) is repeated until the generated event sequences
have reached the desired length.
The `events_to_input`, `events_to_label`, and `class_index_to_event` methods
must be overwritten to be specific to your model.
"""
__metaclass__ = abc.ABCMeta
def _encode(self, events):
"""Returns a SequenceExample for the given event sequence.
Args:
events: An EventSequence object.
Returns:
A tf.train.SequenceExample containing inputs and labels.
"""
inputs = []
labels = []
for i in range(len(events) - 1):
inputs.append(self.events_to_input(events, i))
labels.append(self.events_to_label(events, i + 1))
return sequence_example_lib.make_sequence_example(inputs, labels)
@abc.abstractproperty
def input_size(self):
"""The size of the input vector used by this model.
Returns:
An integer, the length of the list returned by self.events_to_input.
"""
pass
@abc.abstractproperty
def num_classes(self):
"""The range of labels used by this model.
Returns:
An integer, the range of integers that can be returned by
self.events_to_label.
"""
pass
@abc.abstractmethod
def events_to_input(self, events, position):
"""Returns the input vector for the event at the given position.
Args:
events: An EventSequence object.
position: An integer event position in the sequence.
Returns:
An input vector, a self.input_size length list of floats.
"""
pass
@abc.abstractmethod
def events_to_label(self, events, position):
"""Returns the label for the event at the given position.
Args:
events: An EventSequence object.
position: An integer event position in the sequence.
Returns:
A label, an integer in the range [0, self.num_classes).
"""
pass
def get_inputs_batch(self, event_sequences, full_length=False):
"""Returns an inputs batch for the given event sequences.
Args:
event_sequences: A list of EventSequence objects.
full_length: If True, the inputs batch will be for the full length of
each event sequence. If False, the inputs batch will only be for the
last event of each event sequence. A full-length inputs batch is used
for the first step of extending the event sequences, since the RNN
cell state needs to be initialized with the priming sequence. For
subsequent generation steps, only a last-event inputs batch is used.
Returns:
An inputs batch. If `full_length` is True, the shape will be
[len(event_sequences), len(event_sequences[0]), INPUT_SIZE]. If
`full_length` is False, the shape will be
[len(event_sequences), 1, INPUT_SIZE].
"""
inputs_batch = []
for events in event_sequences:
inputs = []
if full_length and len(event_sequences):
for i in range(len(events)):
inputs.append(self.events_to_input(events, i))
else:
inputs.append(self.events_to_input(events, len(events) - 1))
inputs_batch.append(inputs)
return inputs_batch
@abc.abstractmethod
def class_index_to_event(self, class_index, events):
"""Returns the event for the given class index.
This is the reverse process of the self.events_to_label method.
Args:
class_index: An integer in the range [0, self.num_classes).
events: An EventSequence object.
Returns:
An event value.
"""
pass
def extend_event_sequences(self, event_sequences, softmax):
"""Extends the event_sequences by sampling the softmax probabilities.
Args:
event_sequences: A list of EventSequence objects.
softmax: A list of softmax probability vectors. The list of softmaxes
should be the same length as the list of event_sequences.
"""
num_classes = len(softmax[0][0])
for i in xrange(len(event_sequences)):
chosen_class = np.random.choice(num_classes, p=softmax[i][-1])
event = self.class_index_to_event(chosen_class, event_sequences[i])
event_sequences[i].append_event(event)