Module note_seq.performance_lib
Utility functions for working with polyphonic performances.
Expand source code
# Copyright 2021 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.
"""Utility functions for working with polyphonic performances."""
import abc
import collections
import math
from absl import logging
import attr
from note_seq import constants
from note_seq import events_lib
from note_seq import sequences_lib
from note_seq.protobuf import music_pb2
MAX_MIDI_PITCH = constants.MAX_MIDI_PITCH
MIN_MIDI_PITCH = constants.MIN_MIDI_PITCH
MAX_MIDI_VELOCITY = constants.MAX_MIDI_VELOCITY
MIN_MIDI_VELOCITY = constants.MIN_MIDI_VELOCITY
MAX_NUM_VELOCITY_BINS = MAX_MIDI_VELOCITY - MIN_MIDI_VELOCITY + 1
STANDARD_PPQ = constants.STANDARD_PPQ
DEFAULT_MAX_SHIFT_STEPS = 100
DEFAULT_MAX_SHIFT_QUARTERS = 4
DEFAULT_PROGRAM = 0
@attr.s(frozen=True)
class PerformanceEvent(object):
"""Class for storing events in a performance."""
event_type = attr.ib()
event_value = attr.ib()
# Start of a new note.
NOTE_ON = 1
# End of a note.
NOTE_OFF = 2
# Shift time forward.
TIME_SHIFT = 3
# Change current velocity.
VELOCITY = 4
# Duration of preceding NOTE_ON.
# For Note-based encoding, used instead of NOTE_OFF events.
DURATION = 5
@event_type.validator
def _check_event(self, attribute, value):
"""Validate event contents."""
del attribute, value # This checks the whole object.
if self.event_type in (PerformanceEvent.NOTE_ON, PerformanceEvent.NOTE_OFF):
if not MIN_MIDI_PITCH <= self.event_value <= MAX_MIDI_PITCH:
raise ValueError('Invalid pitch value: %s' % self.event_value)
elif self.event_type == PerformanceEvent.TIME_SHIFT:
if not 0 <= self.event_value:
raise ValueError('Invalid time shift value: %s' % self.event_value)
elif self.event_type == PerformanceEvent.DURATION:
if not 1 <= self.event_value:
raise ValueError('Invalid duration value: %s' % self.event_value)
elif self.event_type == PerformanceEvent.VELOCITY:
if not 1 <= self.event_value <= MAX_NUM_VELOCITY_BINS:
raise ValueError('Invalid velocity value: %s' % self.event_value)
else:
raise ValueError('Invalid event type: %s' % self.event_type)
def _velocity_bin_size(num_velocity_bins):
return int(math.ceil(
(MAX_MIDI_VELOCITY - MIN_MIDI_VELOCITY + 1) / num_velocity_bins))
def velocity_to_bin(velocity, num_velocity_bins):
return ((velocity - MIN_MIDI_VELOCITY) //
_velocity_bin_size(num_velocity_bins) + 1)
def velocity_bin_to_velocity(velocity_bin, num_velocity_bins):
return (
MIN_MIDI_VELOCITY + (velocity_bin - 1) *
_velocity_bin_size(num_velocity_bins))
def _program_and_is_drum_from_sequence(sequence, instrument=None):
"""Get MIDI program and is_drum from sequence and (optional) instrument.
Args:
sequence: The NoteSequence from which MIDI program and is_drum will be
extracted.
instrument: The instrument in `sequence` from which MIDI program and
is_drum will be extracted, or None to consider all instruments.
Returns:
A tuple containing program and is_drum for the sequence and optional
instrument. If multiple programs are found (or if is_drum is True),
program will be None. If multiple values of is_drum are found, is_drum
will be None.
"""
notes = [note for note in sequence.notes
if instrument is None or note.instrument == instrument]
# Only set program for non-drum tracks.
if all(note.is_drum for note in notes):
is_drum = True
program = None
elif all(not note.is_drum for note in notes):
is_drum = False
programs = set(note.program for note in notes)
program = programs.pop() if len(programs) == 1 else None
else:
is_drum = None
program = None
return program, is_drum
class BasePerformance(events_lib.EventSequence):
"""Stores a polyphonic sequence as a stream of performance events.
Events are PerformanceEvent objects that encode event type and value.
"""
__metaclass__ = abc.ABCMeta
def __init__(self, start_step, num_velocity_bins, max_shift_steps,
program=None, is_drum=None):
"""Construct a BasePerformance.
Args:
start_step: The offset of this sequence relative to the beginning of the
source sequence.
num_velocity_bins: Number of velocity bins to use.
max_shift_steps: Maximum number of steps for a single time-shift event.
program: MIDI program used for this performance, or None if not specified.
is_drum: Whether or not this performance consists of drums, or None if not
specified.
Raises:
ValueError: If `num_velocity_bins` is larger than the number of MIDI
velocity values.
"""
if num_velocity_bins > MAX_MIDI_VELOCITY - MIN_MIDI_VELOCITY + 1:
raise ValueError(
'Number of velocity bins is too large: %d' % num_velocity_bins)
self._start_step = start_step
self._num_velocity_bins = num_velocity_bins
self._max_shift_steps = max_shift_steps
self._program = program
self._is_drum = is_drum
@property
def start_step(self):
return self._start_step
@property
def max_shift_steps(self):
return self._max_shift_steps
@property
def program(self):
return self._program
@property
def is_drum(self):
return self._is_drum
def _append_steps(self, num_steps):
"""Adds steps to the end of the sequence."""
if (self._events and
self._events[-1].event_type == PerformanceEvent.TIME_SHIFT and
self._events[-1].event_value < self._max_shift_steps):
# Last event is already non-maximal time shift. Increase its duration.
added_steps = min(num_steps,
self._max_shift_steps - self._events[-1].event_value)
self._events[-1] = PerformanceEvent(
PerformanceEvent.TIME_SHIFT,
self._events[-1].event_value + added_steps)
num_steps -= added_steps
while num_steps >= self._max_shift_steps:
self._events.append(
PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT,
event_value=self._max_shift_steps))
num_steps -= self._max_shift_steps
if num_steps > 0:
self._events.append(
PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT,
event_value=num_steps))
def _trim_steps(self, num_steps):
"""Trims a given number of steps from the end of the sequence."""
steps_trimmed = 0
while self._events and steps_trimmed < num_steps:
if self._events[-1].event_type == PerformanceEvent.TIME_SHIFT:
if steps_trimmed + self._events[-1].event_value > num_steps:
self._events[-1] = PerformanceEvent(
event_type=PerformanceEvent.TIME_SHIFT,
event_value=(self._events[-1].event_value -
num_steps + steps_trimmed))
steps_trimmed = num_steps
else:
steps_trimmed += self._events[-1].event_value
self._events.pop()
else:
self._events.pop()
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 time shifts to make the
sequence the specified length. If it is too long, it will be truncated to
the requested length.
Args:
steps: How many quantized steps long the event sequence should be.
from_left: Whether to add/remove from the left instead of right.
"""
if from_left:
raise NotImplementedError('from_left is not supported')
if self.num_steps < steps:
self._append_steps(steps - self.num_steps)
elif self.num_steps > steps:
self._trim_steps(self.num_steps - steps)
assert self.num_steps == steps
def append(self, event):
"""Appends the event to the end of the sequence.
Args:
event: The performance event to append to the end.
Raises:
ValueError: If `event` is not a valid performance event.
"""
if not isinstance(event, PerformanceEvent):
raise ValueError('Invalid performance event: %s' % event)
self._events.append(event)
def truncate(self, num_events):
"""Truncates this Performance to the specified number of events.
Args:
num_events: The number of events to which this performance will be
truncated.
"""
self._events = self._events[:num_events]
def __len__(self):
"""How many events are in this sequence.
Returns:
Number of events as an integer.
"""
return len(self._events)
def __getitem__(self, i):
"""Returns the event at the given index."""
return self._events[i]
def __iter__(self):
"""Return an iterator over the events in this sequence."""
return iter(self._events)
def __str__(self):
strs = []
for event in self:
if event.event_type == PerformanceEvent.NOTE_ON:
strs.append('(%s, ON)' % event.event_value)
elif event.event_type == PerformanceEvent.NOTE_OFF:
strs.append('(%s, OFF)' % event.event_value)
elif event.event_type == PerformanceEvent.TIME_SHIFT:
strs.append('(%s, SHIFT)' % event.event_value)
elif event.event_type == PerformanceEvent.VELOCITY:
strs.append('(%s, VELOCITY)' % event.event_value)
else:
raise ValueError('Unknown event type: %s' % event.event_type)
return '\n'.join(strs)
@property
def end_step(self):
return self.start_step + self.num_steps
@property
def num_steps(self):
"""Returns how many steps long this sequence is.
Returns:
Length of the sequence in quantized steps.
"""
steps = 0
for event in self:
if event.event_type == PerformanceEvent.TIME_SHIFT:
steps += event.event_value
return steps
@property
def steps(self):
"""Return a Python list of the time step at each event in this sequence."""
step = self.start_step
result = []
for event in self:
result.append(step)
if event.event_type == PerformanceEvent.TIME_SHIFT:
step += event.event_value
return result
@staticmethod
def _from_quantized_sequence(quantized_sequence, start_step,
num_velocity_bins, max_shift_steps,
instrument=None):
"""Extract a list of events from the given quantized NoteSequence object.
Within a step, new pitches are started with NOTE_ON and existing pitches are
ended with NOTE_OFF. TIME_SHIFT shifts the current step forward in time.
VELOCITY changes the current velocity value that will be applied to all
NOTE_ON events.
Args:
quantized_sequence: A quantized NoteSequence instance.
start_step: Start converting the sequence at this time step.
num_velocity_bins: Number of velocity bins to use. If 0, velocity events
will not be included at all.
max_shift_steps: Maximum number of steps for a single time-shift event.
instrument: If not None, extract only the specified instrument. Otherwise,
extract all instruments into a single event list.
Returns:
A list of events.
"""
notes = [note for note in quantized_sequence.notes
if note.quantized_start_step >= start_step
and (instrument is None or note.instrument == instrument)]
sorted_notes = sorted(notes, key=lambda note: (note.start_time, note.pitch))
# Sort all note start and end events.
onsets = [(note.quantized_start_step, idx, False)
for idx, note in enumerate(sorted_notes)]
offsets = [(note.quantized_end_step, idx, True)
for idx, note in enumerate(sorted_notes)]
note_events = sorted(onsets + offsets)
current_step = start_step
current_velocity_bin = 0
performance_events = []
for step, idx, is_offset in note_events:
if step > current_step:
# Shift time forward from the current step to this event.
while step > current_step + max_shift_steps:
# We need to move further than the maximum shift size.
performance_events.append(
PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT,
event_value=max_shift_steps))
current_step += max_shift_steps
performance_events.append(
PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT,
event_value=int(step - current_step)))
current_step = step
# If we're using velocity and this note's velocity is different from the
# current velocity, change the current velocity.
if num_velocity_bins:
velocity_bin = velocity_to_bin(
sorted_notes[idx].velocity, num_velocity_bins)
if not is_offset and velocity_bin != current_velocity_bin:
current_velocity_bin = velocity_bin
performance_events.append(
PerformanceEvent(event_type=PerformanceEvent.VELOCITY,
event_value=current_velocity_bin))
# Add a performance event for this note on/off.
event_type = (
PerformanceEvent.NOTE_OFF if is_offset else PerformanceEvent.NOTE_ON)
performance_events.append(
PerformanceEvent(event_type=event_type,
event_value=sorted_notes[idx].pitch))
return performance_events
@abc.abstractmethod
def to_sequence(self, velocity, instrument, program, max_note_duration=None):
"""Converts the Performance to NoteSequence proto.
Args:
velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive).
If the performance contains velocity events, those will be used
instead.
instrument: MIDI instrument to give each note.
program: MIDI program to give each note, or None to use the program
associated with the Performance (or the default program if none
exists).
max_note_duration: Maximum note duration in seconds to allow. Notes longer
than this will be truncated. If None, notes can be any length.
Returns:
A NoteSequence proto.
"""
pass
def _to_sequence(self, seconds_per_step, velocity, instrument, program,
max_note_duration=None):
sequence_start_time = self.start_step * seconds_per_step
sequence = music_pb2.NoteSequence()
sequence.ticks_per_quarter = STANDARD_PPQ
step = 0
if program is None:
# Use program associated with the performance (or default program).
program = self.program if self.program is not None else DEFAULT_PROGRAM
is_drum = self.is_drum if self.is_drum is not None else False
# Map pitch to list because one pitch may be active multiple times.
pitch_start_steps_and_velocities = collections.defaultdict(list)
for i, event in enumerate(self):
if event.event_type == PerformanceEvent.NOTE_ON:
pitch_start_steps_and_velocities[event.event_value].append(
(step, velocity))
elif event.event_type == PerformanceEvent.NOTE_OFF:
if not pitch_start_steps_and_velocities[event.event_value]:
logging.debug(
'Ignoring NOTE_OFF at position %d with no previous NOTE_ON', i)
else:
# Create a note for the pitch that is now ending.
pitch_start_step, pitch_velocity = pitch_start_steps_and_velocities[
event.event_value][0]
pitch_start_steps_and_velocities[event.event_value] = (
pitch_start_steps_and_velocities[event.event_value][1:])
if step == pitch_start_step:
logging.debug(
'Ignoring note with zero duration at step %d', step)
continue
note = sequence.notes.add()
note.start_time = (pitch_start_step * seconds_per_step +
sequence_start_time)
note.end_time = step * seconds_per_step + sequence_start_time
if (max_note_duration and
note.end_time - note.start_time > max_note_duration):
note.end_time = note.start_time + max_note_duration
note.pitch = event.event_value
note.velocity = pitch_velocity
note.instrument = instrument
note.program = program
note.is_drum = is_drum
if note.end_time > sequence.total_time:
sequence.total_time = note.end_time
elif event.event_type == PerformanceEvent.TIME_SHIFT:
step += event.event_value
elif event.event_type == PerformanceEvent.VELOCITY:
assert self._num_velocity_bins
velocity = velocity_bin_to_velocity(
event.event_value, self._num_velocity_bins)
else:
raise ValueError('Unknown event type: %s' % event.event_type)
# There could be remaining pitches that were never ended. End them now
# and create notes.
for pitch in pitch_start_steps_and_velocities:
for pitch_start_step, pitch_velocity in pitch_start_steps_and_velocities[
pitch]:
if step == pitch_start_step:
logging.debug('Ignoring note with zero duration at step %d', step)
continue
note = sequence.notes.add()
note.start_time = (pitch_start_step * seconds_per_step +
sequence_start_time)
note.end_time = step * seconds_per_step + sequence_start_time
if (max_note_duration and
note.end_time - note.start_time > max_note_duration):
note.end_time = note.start_time + max_note_duration
note.pitch = pitch
note.velocity = pitch_velocity
note.instrument = instrument
note.program = program
note.is_drum = is_drum
if note.end_time > sequence.total_time:
sequence.total_time = note.end_time
return sequence
class Performance(BasePerformance):
"""Performance with absolute timing and unknown meter."""
def __init__(self, quantized_sequence=None, steps_per_second=None,
start_step=0, num_velocity_bins=0,
max_shift_steps=DEFAULT_MAX_SHIFT_STEPS, instrument=None,
program=None, is_drum=None):
"""Construct a Performance.
Either quantized_sequence or steps_per_second should be supplied.
Args:
quantized_sequence: A quantized NoteSequence proto.
steps_per_second: Number of quantized time steps per second, if using
absolute quantization.
start_step: The offset of this sequence relative to the
beginning of the source sequence. If a quantized sequence is used as
input, only notes starting after this step will be considered.
num_velocity_bins: Number of velocity bins to use. If 0, velocity events
will not be included at all.
max_shift_steps: Maximum number of steps for a single time-shift event.
instrument: If not None, extract only the specified instrument from
`quantized_sequence`. Otherwise, extract all instruments.
program: MIDI program used for this performance, or None if not specified.
Ignored if `quantized_sequence` is provided.
is_drum: Whether or not this performance consists of drums, or None if not
specified. Ignored if `quantized_sequence` is provided.
Raises:
ValueError: If both or neither of `quantized_sequence` or
`steps_per_second` is specified.
"""
if (quantized_sequence, steps_per_second).count(None) != 1:
raise ValueError(
'Must specify exactly one of quantized_sequence or steps_per_second')
if quantized_sequence:
sequences_lib.assert_is_absolute_quantized_sequence(quantized_sequence)
self._steps_per_second = (
quantized_sequence.quantization_info.steps_per_second)
self._events = self._from_quantized_sequence(
quantized_sequence, start_step, num_velocity_bins,
max_shift_steps=max_shift_steps, instrument=instrument)
program, is_drum = _program_and_is_drum_from_sequence(
quantized_sequence, instrument)
else:
self._steps_per_second = steps_per_second
self._events = []
super(Performance, self).__init__(
start_step=start_step,
num_velocity_bins=num_velocity_bins,
max_shift_steps=max_shift_steps,
program=program,
is_drum=is_drum)
@property
def steps_per_second(self):
return self._steps_per_second
def to_sequence(self,
velocity=100,
instrument=0,
program=None,
max_note_duration=None):
"""Converts the Performance to NoteSequence proto.
Args:
velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive).
If the performance contains velocity events, those will be used
instead.
instrument: MIDI instrument to give each note.
program: MIDI program to give each note, or None to use the program
associated with the Performance (or the default program if none
exists).
max_note_duration: Maximum note duration in seconds to allow. Notes longer
than this will be truncated. If None, notes can be any length.
Returns:
A NoteSequence proto.
"""
seconds_per_step = 1.0 / self.steps_per_second
return self._to_sequence(
seconds_per_step=seconds_per_step,
velocity=velocity,
instrument=instrument,
program=program,
max_note_duration=max_note_duration)
class MetricPerformance(BasePerformance):
"""Performance with quarter-note relative timing."""
def __init__(self, quantized_sequence=None, steps_per_quarter=None,
start_step=0, num_velocity_bins=0,
max_shift_quarters=DEFAULT_MAX_SHIFT_QUARTERS, instrument=None,
program=None, is_drum=None):
"""Construct a MetricPerformance.
Either quantized_sequence or steps_per_quarter should be supplied.
Args:
quantized_sequence: A quantized NoteSequence proto.
steps_per_quarter: Number of quantized time steps per quarter note, if
using metric quantization.
start_step: The offset of this sequence relative to the
beginning of the source sequence. If a quantized sequence is used as
input, only notes starting after this step will be considered.
num_velocity_bins: Number of velocity bins to use. If 0, velocity events
will not be included at all.
max_shift_quarters: Maximum number of quarter notes for a single time-
shift event.
instrument: If not None, extract only the specified instrument from
`quantized_sequence`. Otherwise, extract all instruments.
program: MIDI program used for this performance, or None if not specified.
Ignored if `quantized_sequence` is provided.
is_drum: Whether or not this performance consists of drums, or None if not
specified. Ignored if `quantized_sequence` is provided.
Raises:
ValueError: If both or neither of `quantized_sequence` or
`steps_per_quarter` is specified.
"""
if (quantized_sequence, steps_per_quarter).count(None) != 1:
raise ValueError(
'Must specify exactly one of quantized_sequence or steps_per_quarter')
if quantized_sequence:
sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)
self._steps_per_quarter = (
quantized_sequence.quantization_info.steps_per_quarter)
self._events = self._from_quantized_sequence(
quantized_sequence, start_step, num_velocity_bins,
max_shift_steps=self._steps_per_quarter * max_shift_quarters,
instrument=instrument)
program, is_drum = _program_and_is_drum_from_sequence(
quantized_sequence, instrument)
else:
self._steps_per_quarter = steps_per_quarter
self._events = []
super(MetricPerformance, self).__init__(
start_step=start_step,
num_velocity_bins=num_velocity_bins,
max_shift_steps=self._steps_per_quarter * max_shift_quarters,
program=program,
is_drum=is_drum)
@property
def steps_per_quarter(self):
return self._steps_per_quarter
def to_sequence(self,
velocity=100,
instrument=0,
program=None,
max_note_duration=None,
qpm=120.0):
"""Converts the Performance to NoteSequence proto.
Args:
velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive).
If the performance contains velocity events, those will be used
instead.
instrument: MIDI instrument to give each note.
program: MIDI program to give each note, or None to use the program
associated with the Performance (or the default program if none
exists).
max_note_duration: Maximum note duration in seconds to allow. Notes longer
than this will be truncated. If None, notes can be any length.
qpm: The tempo to use, in quarter notes per minute.
Returns:
A NoteSequence proto.
"""
seconds_per_step = 60.0 / (self.steps_per_quarter * qpm)
sequence = self._to_sequence(
seconds_per_step=seconds_per_step,
velocity=velocity,
instrument=instrument,
program=program,
max_note_duration=max_note_duration)
sequence.tempos.add(qpm=qpm)
return sequence
class NotePerformanceError(Exception):
pass
class TooManyTimeShiftStepsError(NotePerformanceError):
pass
class TooManyDurationStepsError(NotePerformanceError):
pass
class NotePerformance(BasePerformance):
"""Stores a polyphonic sequence as a stream of performance events.
Events are PerformanceEvent objects that encode event type and value.
In this version, the performance is encoded in 4-event tuples:
TIME_SHIFT, NOTE_ON, VELOCITY, DURATION.
"""
def __init__(self, quantized_sequence, num_velocity_bins, instrument=0,
start_step=0, max_shift_steps=1000, max_duration_steps=1000):
"""Construct a NotePerformance.
Args:
quantized_sequence: A quantized NoteSequence proto.
num_velocity_bins: Number of velocity bins to use.
instrument: If not None, extract only the specified instrument from
`quantized_sequence`. Otherwise, extract all instruments.
start_step: The offset of this sequence relative to the beginning of the
source sequence.
max_shift_steps: Maximum number of steps for a time-shift event.
max_duration_steps: Maximum number of steps for a duration event.
Raises:
ValueError: If `num_velocity_bins` is larger than the number of MIDI
velocity values.
"""
program, is_drum = _program_and_is_drum_from_sequence(
quantized_sequence, instrument)
super(NotePerformance, self).__init__(
start_step=start_step,
num_velocity_bins=num_velocity_bins,
max_shift_steps=max_shift_steps,
program=program,
is_drum=is_drum)
self._max_duration_steps = max_duration_steps
sequences_lib.assert_is_absolute_quantized_sequence(quantized_sequence)
self._steps_per_second = (
quantized_sequence.quantization_info.steps_per_second)
self._events = self._from_quantized_sequence(
quantized_sequence, instrument)
@property
def steps_per_second(self):
return self._steps_per_second
def set_length(self, steps, from_left=False):
# This is not actually implemented, but to avoid raising exceptions during
# generation just return instead of raising NotImplementedError.
# TODO(fjord): Implement this.
return
def append(self, event):
"""Appends the event to the end of the sequence.
Args:
event: The performance event tuple to append to the end.
Raises:
ValueError: If `event` is not a valid performance event tuple.
"""
if not isinstance(event, tuple):
raise ValueError('Invalid performance event tuple: %s' % event)
self._events.append(event)
def __str__(self):
strs = []
for event in self:
strs.append('TIME_SHIFT<%s>, NOTE_ON<%s>, VELOCITY<%s>, DURATION<%s>' % (
event[0].event_value, event[1].event_value, event[2].event_value,
event[3].event_value))
return '\n'.join(strs)
@property
def num_steps(self):
"""Returns how many steps long this sequence is.
Returns:
Length of the sequence in quantized steps.
"""
steps = 0
for event in self._events:
steps += event[0].event_value
if self._events:
steps += self._events[-1][3].event_value
return steps
@property
def steps(self):
"""Return a Python list of the time step at each event in this sequence."""
step = self.start_step
result = []
for event in self:
step += event[0].event_value
result.append(step)
return result
def _from_quantized_sequence(self, quantized_sequence, instrument):
"""Extract a list of events from the given quantized NoteSequence object.
Within a step, new pitches are started with NOTE_ON and existing pitches are
ended with NOTE_OFF. TIME_SHIFT shifts the current step forward in time.
VELOCITY changes the current velocity value that will be applied to all
NOTE_ON events.
Args:
quantized_sequence: A quantized NoteSequence instance.
instrument: If not None, extract only the specified instrument. Otherwise,
extract all instruments into a single event list.
Returns:
A list of events.
Raises:
TooManyTimeShiftStepsError: If the maximum number of time
shift steps is exceeded.
TooManyDurationStepsError: If the maximum number of duration
shift steps is exceeded.
"""
notes = [note for note in quantized_sequence.notes
if note.quantized_start_step >= self.start_step
and (instrument is None or note.instrument == instrument)]
sorted_notes = sorted(notes, key=lambda note: (note.start_time, note.pitch))
current_step = self.start_step
performance_events = []
for note in sorted_notes:
sub_events = []
# TIME_SHIFT
time_shift_steps = note.quantized_start_step - current_step
if time_shift_steps > self._max_shift_steps:
raise TooManyTimeShiftStepsError(
'Too many steps for timeshift: %d' % time_shift_steps)
else:
sub_events.append(
PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT,
event_value=time_shift_steps))
current_step = note.quantized_start_step
# NOTE_ON
sub_events.append(
PerformanceEvent(event_type=PerformanceEvent.NOTE_ON,
event_value=note.pitch))
# VELOCITY
velocity_bin = velocity_to_bin(note.velocity, self._num_velocity_bins)
sub_events.append(
PerformanceEvent(event_type=PerformanceEvent.VELOCITY,
event_value=velocity_bin))
# DURATION
duration_steps = note.quantized_end_step - note.quantized_start_step
if duration_steps > self._max_duration_steps:
raise TooManyDurationStepsError(
'Too many steps for duration: %s' % note)
sub_events.append(
PerformanceEvent(event_type=PerformanceEvent.DURATION,
event_value=duration_steps))
performance_events.append(tuple(sub_events))
return performance_events
def to_sequence(self, instrument=0, program=None, max_note_duration=None):
"""Converts the Performance to NoteSequence proto.
Args:
instrument: MIDI instrument to give each note.
program: MIDI program to give each note, or None to use the program
associated with the Performance (or the default program if none
exists).
max_note_duration: Not used in this implementation.
Returns:
A NoteSequence proto.
"""
seconds_per_step = 1.0 / self.steps_per_second
sequence_start_time = self.start_step * seconds_per_step
sequence = music_pb2.NoteSequence()
sequence.ticks_per_quarter = STANDARD_PPQ
step = 0
if program is None:
# Use program associated with the performance (or default program).
program = self.program if self.program is not None else DEFAULT_PROGRAM
is_drum = self.is_drum if self.is_drum is not None else False
for event in self:
step += event[0].event_value
note = sequence.notes.add()
note.start_time = step * seconds_per_step + sequence_start_time
note.end_time = ((step + event[3].event_value) * seconds_per_step +
sequence_start_time)
note.pitch = event[1].event_value
note.velocity = velocity_bin_to_velocity(
event[2].event_value, self._num_velocity_bins)
note.instrument = instrument
note.program = program
note.is_drum = is_drum
if note.end_time > sequence.total_time:
sequence.total_time = note.end_time
return sequence
Functions
def velocity_bin_to_velocity(velocity_bin, num_velocity_bins)
-
Expand source code
def velocity_bin_to_velocity(velocity_bin, num_velocity_bins): return ( MIN_MIDI_VELOCITY + (velocity_bin - 1) * _velocity_bin_size(num_velocity_bins))
def velocity_to_bin(velocity, num_velocity_bins)
-
Expand source code
def velocity_to_bin(velocity, num_velocity_bins): return ((velocity - MIN_MIDI_VELOCITY) // _velocity_bin_size(num_velocity_bins) + 1)
Classes
class BasePerformance (start_step, num_velocity_bins, max_shift_steps, program=None, is_drum=None)
-
Stores a polyphonic sequence as a stream of performance events.
Events are PerformanceEvent objects that encode event type and value.
Construct a BasePerformance.
Args
start_step
- The offset of this sequence relative to the beginning of the source sequence.
num_velocity_bins
- Number of velocity bins to use.
max_shift_steps
- Maximum number of steps for a single time-shift event.
program
- MIDI program used for this performance, or None if not specified.
is_drum
- Whether or not this performance consists of drums, or None if not specified.
Raises
ValueError
- If
num_velocity_bins
is larger than the number of MIDI velocity values.
Expand source code
class BasePerformance(events_lib.EventSequence): """Stores a polyphonic sequence as a stream of performance events. Events are PerformanceEvent objects that encode event type and value. """ __metaclass__ = abc.ABCMeta def __init__(self, start_step, num_velocity_bins, max_shift_steps, program=None, is_drum=None): """Construct a BasePerformance. Args: start_step: The offset of this sequence relative to the beginning of the source sequence. num_velocity_bins: Number of velocity bins to use. max_shift_steps: Maximum number of steps for a single time-shift event. program: MIDI program used for this performance, or None if not specified. is_drum: Whether or not this performance consists of drums, or None if not specified. Raises: ValueError: If `num_velocity_bins` is larger than the number of MIDI velocity values. """ if num_velocity_bins > MAX_MIDI_VELOCITY - MIN_MIDI_VELOCITY + 1: raise ValueError( 'Number of velocity bins is too large: %d' % num_velocity_bins) self._start_step = start_step self._num_velocity_bins = num_velocity_bins self._max_shift_steps = max_shift_steps self._program = program self._is_drum = is_drum @property def start_step(self): return self._start_step @property def max_shift_steps(self): return self._max_shift_steps @property def program(self): return self._program @property def is_drum(self): return self._is_drum def _append_steps(self, num_steps): """Adds steps to the end of the sequence.""" if (self._events and self._events[-1].event_type == PerformanceEvent.TIME_SHIFT and self._events[-1].event_value < self._max_shift_steps): # Last event is already non-maximal time shift. Increase its duration. added_steps = min(num_steps, self._max_shift_steps - self._events[-1].event_value) self._events[-1] = PerformanceEvent( PerformanceEvent.TIME_SHIFT, self._events[-1].event_value + added_steps) num_steps -= added_steps while num_steps >= self._max_shift_steps: self._events.append( PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT, event_value=self._max_shift_steps)) num_steps -= self._max_shift_steps if num_steps > 0: self._events.append( PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT, event_value=num_steps)) def _trim_steps(self, num_steps): """Trims a given number of steps from the end of the sequence.""" steps_trimmed = 0 while self._events and steps_trimmed < num_steps: if self._events[-1].event_type == PerformanceEvent.TIME_SHIFT: if steps_trimmed + self._events[-1].event_value > num_steps: self._events[-1] = PerformanceEvent( event_type=PerformanceEvent.TIME_SHIFT, event_value=(self._events[-1].event_value - num_steps + steps_trimmed)) steps_trimmed = num_steps else: steps_trimmed += self._events[-1].event_value self._events.pop() else: self._events.pop() 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 time shifts to make the sequence the specified length. If it is too long, it will be truncated to the requested length. Args: steps: How many quantized steps long the event sequence should be. from_left: Whether to add/remove from the left instead of right. """ if from_left: raise NotImplementedError('from_left is not supported') if self.num_steps < steps: self._append_steps(steps - self.num_steps) elif self.num_steps > steps: self._trim_steps(self.num_steps - steps) assert self.num_steps == steps def append(self, event): """Appends the event to the end of the sequence. Args: event: The performance event to append to the end. Raises: ValueError: If `event` is not a valid performance event. """ if not isinstance(event, PerformanceEvent): raise ValueError('Invalid performance event: %s' % event) self._events.append(event) def truncate(self, num_events): """Truncates this Performance to the specified number of events. Args: num_events: The number of events to which this performance will be truncated. """ self._events = self._events[:num_events] def __len__(self): """How many events are in this sequence. Returns: Number of events as an integer. """ return len(self._events) def __getitem__(self, i): """Returns the event at the given index.""" return self._events[i] def __iter__(self): """Return an iterator over the events in this sequence.""" return iter(self._events) def __str__(self): strs = [] for event in self: if event.event_type == PerformanceEvent.NOTE_ON: strs.append('(%s, ON)' % event.event_value) elif event.event_type == PerformanceEvent.NOTE_OFF: strs.append('(%s, OFF)' % event.event_value) elif event.event_type == PerformanceEvent.TIME_SHIFT: strs.append('(%s, SHIFT)' % event.event_value) elif event.event_type == PerformanceEvent.VELOCITY: strs.append('(%s, VELOCITY)' % event.event_value) else: raise ValueError('Unknown event type: %s' % event.event_type) return '\n'.join(strs) @property def end_step(self): return self.start_step + self.num_steps @property def num_steps(self): """Returns how many steps long this sequence is. Returns: Length of the sequence in quantized steps. """ steps = 0 for event in self: if event.event_type == PerformanceEvent.TIME_SHIFT: steps += event.event_value return steps @property def steps(self): """Return a Python list of the time step at each event in this sequence.""" step = self.start_step result = [] for event in self: result.append(step) if event.event_type == PerformanceEvent.TIME_SHIFT: step += event.event_value return result @staticmethod def _from_quantized_sequence(quantized_sequence, start_step, num_velocity_bins, max_shift_steps, instrument=None): """Extract a list of events from the given quantized NoteSequence object. Within a step, new pitches are started with NOTE_ON and existing pitches are ended with NOTE_OFF. TIME_SHIFT shifts the current step forward in time. VELOCITY changes the current velocity value that will be applied to all NOTE_ON events. Args: quantized_sequence: A quantized NoteSequence instance. start_step: Start converting the sequence at this time step. num_velocity_bins: Number of velocity bins to use. If 0, velocity events will not be included at all. max_shift_steps: Maximum number of steps for a single time-shift event. instrument: If not None, extract only the specified instrument. Otherwise, extract all instruments into a single event list. Returns: A list of events. """ notes = [note for note in quantized_sequence.notes if note.quantized_start_step >= start_step and (instrument is None or note.instrument == instrument)] sorted_notes = sorted(notes, key=lambda note: (note.start_time, note.pitch)) # Sort all note start and end events. onsets = [(note.quantized_start_step, idx, False) for idx, note in enumerate(sorted_notes)] offsets = [(note.quantized_end_step, idx, True) for idx, note in enumerate(sorted_notes)] note_events = sorted(onsets + offsets) current_step = start_step current_velocity_bin = 0 performance_events = [] for step, idx, is_offset in note_events: if step > current_step: # Shift time forward from the current step to this event. while step > current_step + max_shift_steps: # We need to move further than the maximum shift size. performance_events.append( PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT, event_value=max_shift_steps)) current_step += max_shift_steps performance_events.append( PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT, event_value=int(step - current_step))) current_step = step # If we're using velocity and this note's velocity is different from the # current velocity, change the current velocity. if num_velocity_bins: velocity_bin = velocity_to_bin( sorted_notes[idx].velocity, num_velocity_bins) if not is_offset and velocity_bin != current_velocity_bin: current_velocity_bin = velocity_bin performance_events.append( PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=current_velocity_bin)) # Add a performance event for this note on/off. event_type = ( PerformanceEvent.NOTE_OFF if is_offset else PerformanceEvent.NOTE_ON) performance_events.append( PerformanceEvent(event_type=event_type, event_value=sorted_notes[idx].pitch)) return performance_events @abc.abstractmethod def to_sequence(self, velocity, instrument, program, max_note_duration=None): """Converts the Performance to NoteSequence proto. Args: velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead. instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length. Returns: A NoteSequence proto. """ pass def _to_sequence(self, seconds_per_step, velocity, instrument, program, max_note_duration=None): sequence_start_time = self.start_step * seconds_per_step sequence = music_pb2.NoteSequence() sequence.ticks_per_quarter = STANDARD_PPQ step = 0 if program is None: # Use program associated with the performance (or default program). program = self.program if self.program is not None else DEFAULT_PROGRAM is_drum = self.is_drum if self.is_drum is not None else False # Map pitch to list because one pitch may be active multiple times. pitch_start_steps_and_velocities = collections.defaultdict(list) for i, event in enumerate(self): if event.event_type == PerformanceEvent.NOTE_ON: pitch_start_steps_and_velocities[event.event_value].append( (step, velocity)) elif event.event_type == PerformanceEvent.NOTE_OFF: if not pitch_start_steps_and_velocities[event.event_value]: logging.debug( 'Ignoring NOTE_OFF at position %d with no previous NOTE_ON', i) else: # Create a note for the pitch that is now ending. pitch_start_step, pitch_velocity = pitch_start_steps_and_velocities[ event.event_value][0] pitch_start_steps_and_velocities[event.event_value] = ( pitch_start_steps_and_velocities[event.event_value][1:]) if step == pitch_start_step: logging.debug( 'Ignoring note with zero duration at step %d', step) continue note = sequence.notes.add() note.start_time = (pitch_start_step * seconds_per_step + sequence_start_time) note.end_time = step * seconds_per_step + sequence_start_time if (max_note_duration and note.end_time - note.start_time > max_note_duration): note.end_time = note.start_time + max_note_duration note.pitch = event.event_value note.velocity = pitch_velocity note.instrument = instrument note.program = program note.is_drum = is_drum if note.end_time > sequence.total_time: sequence.total_time = note.end_time elif event.event_type == PerformanceEvent.TIME_SHIFT: step += event.event_value elif event.event_type == PerformanceEvent.VELOCITY: assert self._num_velocity_bins velocity = velocity_bin_to_velocity( event.event_value, self._num_velocity_bins) else: raise ValueError('Unknown event type: %s' % event.event_type) # There could be remaining pitches that were never ended. End them now # and create notes. for pitch in pitch_start_steps_and_velocities: for pitch_start_step, pitch_velocity in pitch_start_steps_and_velocities[ pitch]: if step == pitch_start_step: logging.debug('Ignoring note with zero duration at step %d', step) continue note = sequence.notes.add() note.start_time = (pitch_start_step * seconds_per_step + sequence_start_time) note.end_time = step * seconds_per_step + sequence_start_time if (max_note_duration and note.end_time - note.start_time > max_note_duration): note.end_time = note.start_time + max_note_duration note.pitch = pitch note.velocity = pitch_velocity note.instrument = instrument note.program = program note.is_drum = is_drum if note.end_time > sequence.total_time: sequence.total_time = note.end_time return sequence
Ancestors
Subclasses
Instance variables
var end_step
-
Expand source code
@property def end_step(self): return self.start_step + self.num_steps
var is_drum
-
Expand source code
@property def is_drum(self): return self._is_drum
var max_shift_steps
-
Expand source code
@property def max_shift_steps(self): return self._max_shift_steps
var num_steps
-
Returns how many steps long this sequence is.
Returns
Length of the sequence in quantized steps.
Expand source code
@property def num_steps(self): """Returns how many steps long this sequence is. Returns: Length of the sequence in quantized steps. """ steps = 0 for event in self: if event.event_type == PerformanceEvent.TIME_SHIFT: steps += event.event_value return steps
var program
-
Expand source code
@property def program(self): return self._program
var start_step
-
Expand source code
@property def start_step(self): return self._start_step
var steps
-
Return a Python list of the time step at each event in this sequence.
Expand source code
@property def steps(self): """Return a Python list of the time step at each event in this sequence.""" step = self.start_step result = [] for event in self: result.append(step) if event.event_type == PerformanceEvent.TIME_SHIFT: step += event.event_value return result
Methods
def append(self, event)
-
Appends the event to the end of the sequence.
Args
event
- The performance event to append to the end.
Raises
ValueError
- If
event
is not a valid performance event.
Expand source code
def append(self, event): """Appends the event to the end of the sequence. Args: event: The performance event to append to the end. Raises: ValueError: If `event` is not a valid performance event. """ if not isinstance(event, PerformanceEvent): raise ValueError('Invalid performance event: %s' % event) self._events.append(event)
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 time shifts to make the sequence the specified length. If it is too long, it will be truncated to the requested length.
Args
steps
- How many quantized steps long the event sequence should be.
from_left
- Whether to add/remove from the left instead of right.
Expand source code
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 time shifts to make the sequence the specified length. If it is too long, it will be truncated to the requested length. Args: steps: How many quantized steps long the event sequence should be. from_left: Whether to add/remove from the left instead of right. """ if from_left: raise NotImplementedError('from_left is not supported') if self.num_steps < steps: self._append_steps(steps - self.num_steps) elif self.num_steps > steps: self._trim_steps(self.num_steps - steps) assert self.num_steps == steps
def to_sequence(self, velocity, instrument, program, max_note_duration=None)
-
Converts the Performance to NoteSequence proto.
Args
velocity
- MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead.
instrument
- MIDI instrument to give each note.
program
- MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists).
max_note_duration
- Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length.
Returns
A NoteSequence proto.
Expand source code
@abc.abstractmethod def to_sequence(self, velocity, instrument, program, max_note_duration=None): """Converts the Performance to NoteSequence proto. Args: velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead. instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length. Returns: A NoteSequence proto. """ pass
def truncate(self, num_events)
-
Truncates this Performance to the specified number of events.
Args
num_events
- The number of events to which this performance will be truncated.
Expand source code
def truncate(self, num_events): """Truncates this Performance to the specified number of events. Args: num_events: The number of events to which this performance will be truncated. """ self._events = self._events[:num_events]
class MetricPerformance (quantized_sequence=None, steps_per_quarter=None, start_step=0, num_velocity_bins=0, max_shift_quarters=4, instrument=None, program=None, is_drum=None)
-
Performance with quarter-note relative timing.
Construct a MetricPerformance.
Either quantized_sequence or steps_per_quarter should be supplied.
Args
quantized_sequence
- A quantized NoteSequence proto.
steps_per_quarter
- Number of quantized time steps per quarter note, if using metric quantization.
start_step
- The offset of this sequence relative to the beginning of the source sequence. If a quantized sequence is used as input, only notes starting after this step will be considered.
num_velocity_bins
- Number of velocity bins to use. If 0, velocity events will not be included at all.
max_shift_quarters
- Maximum number of quarter notes for a single time- shift event.
instrument
- If not None, extract only the specified instrument from
quantized_sequence
. Otherwise, extract all instruments. program
- MIDI program used for this performance, or None if not specified.
Ignored if
quantized_sequence
is provided. is_drum
- Whether or not this performance consists of drums, or None if not
specified. Ignored if
quantized_sequence
is provided.
Raises
ValueError
- If both or neither of
quantized_sequence
orsteps_per_quarter
is specified.
Expand source code
class MetricPerformance(BasePerformance): """Performance with quarter-note relative timing.""" def __init__(self, quantized_sequence=None, steps_per_quarter=None, start_step=0, num_velocity_bins=0, max_shift_quarters=DEFAULT_MAX_SHIFT_QUARTERS, instrument=None, program=None, is_drum=None): """Construct a MetricPerformance. Either quantized_sequence or steps_per_quarter should be supplied. Args: quantized_sequence: A quantized NoteSequence proto. steps_per_quarter: Number of quantized time steps per quarter note, if using metric quantization. start_step: The offset of this sequence relative to the beginning of the source sequence. If a quantized sequence is used as input, only notes starting after this step will be considered. num_velocity_bins: Number of velocity bins to use. If 0, velocity events will not be included at all. max_shift_quarters: Maximum number of quarter notes for a single time- shift event. instrument: If not None, extract only the specified instrument from `quantized_sequence`. Otherwise, extract all instruments. program: MIDI program used for this performance, or None if not specified. Ignored if `quantized_sequence` is provided. is_drum: Whether or not this performance consists of drums, or None if not specified. Ignored if `quantized_sequence` is provided. Raises: ValueError: If both or neither of `quantized_sequence` or `steps_per_quarter` is specified. """ if (quantized_sequence, steps_per_quarter).count(None) != 1: raise ValueError( 'Must specify exactly one of quantized_sequence or steps_per_quarter') if quantized_sequence: sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence) self._steps_per_quarter = ( quantized_sequence.quantization_info.steps_per_quarter) self._events = self._from_quantized_sequence( quantized_sequence, start_step, num_velocity_bins, max_shift_steps=self._steps_per_quarter * max_shift_quarters, instrument=instrument) program, is_drum = _program_and_is_drum_from_sequence( quantized_sequence, instrument) else: self._steps_per_quarter = steps_per_quarter self._events = [] super(MetricPerformance, self).__init__( start_step=start_step, num_velocity_bins=num_velocity_bins, max_shift_steps=self._steps_per_quarter * max_shift_quarters, program=program, is_drum=is_drum) @property def steps_per_quarter(self): return self._steps_per_quarter def to_sequence(self, velocity=100, instrument=0, program=None, max_note_duration=None, qpm=120.0): """Converts the Performance to NoteSequence proto. Args: velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead. instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length. qpm: The tempo to use, in quarter notes per minute. Returns: A NoteSequence proto. """ seconds_per_step = 60.0 / (self.steps_per_quarter * qpm) sequence = self._to_sequence( seconds_per_step=seconds_per_step, velocity=velocity, instrument=instrument, program=program, max_note_duration=max_note_duration) sequence.tempos.add(qpm=qpm) return sequence
Ancestors
Instance variables
var steps_per_quarter
-
Expand source code
@property def steps_per_quarter(self): return self._steps_per_quarter
Methods
def to_sequence(self, velocity=100, instrument=0, program=None, max_note_duration=None, qpm=120.0)
-
Converts the Performance to NoteSequence proto.
Args
velocity
- MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead.
instrument
- MIDI instrument to give each note.
program
- MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists).
max_note_duration
- Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length.
qpm
- The tempo to use, in quarter notes per minute.
Returns
A NoteSequence proto.
Expand source code
def to_sequence(self, velocity=100, instrument=0, program=None, max_note_duration=None, qpm=120.0): """Converts the Performance to NoteSequence proto. Args: velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead. instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length. qpm: The tempo to use, in quarter notes per minute. Returns: A NoteSequence proto. """ seconds_per_step = 60.0 / (self.steps_per_quarter * qpm) sequence = self._to_sequence( seconds_per_step=seconds_per_step, velocity=velocity, instrument=instrument, program=program, max_note_duration=max_note_duration) sequence.tempos.add(qpm=qpm) return sequence
Inherited members
class NotePerformance (quantized_sequence, num_velocity_bins, instrument=0, start_step=0, max_shift_steps=1000, max_duration_steps=1000)
-
Stores a polyphonic sequence as a stream of performance events.
Events are PerformanceEvent objects that encode event type and value. In this version, the performance is encoded in 4-event tuples: TIME_SHIFT, NOTE_ON, VELOCITY, DURATION.
Construct a NotePerformance.
Args
quantized_sequence
- A quantized NoteSequence proto.
num_velocity_bins
- Number of velocity bins to use.
instrument
- If not None, extract only the specified instrument from
quantized_sequence
. Otherwise, extract all instruments. start_step
- The offset of this sequence relative to the beginning of the source sequence.
max_shift_steps
- Maximum number of steps for a time-shift event.
max_duration_steps
- Maximum number of steps for a duration event.
Raises
ValueError
- If
num_velocity_bins
is larger than the number of MIDI velocity values.
Expand source code
class NotePerformance(BasePerformance): """Stores a polyphonic sequence as a stream of performance events. Events are PerformanceEvent objects that encode event type and value. In this version, the performance is encoded in 4-event tuples: TIME_SHIFT, NOTE_ON, VELOCITY, DURATION. """ def __init__(self, quantized_sequence, num_velocity_bins, instrument=0, start_step=0, max_shift_steps=1000, max_duration_steps=1000): """Construct a NotePerformance. Args: quantized_sequence: A quantized NoteSequence proto. num_velocity_bins: Number of velocity bins to use. instrument: If not None, extract only the specified instrument from `quantized_sequence`. Otherwise, extract all instruments. start_step: The offset of this sequence relative to the beginning of the source sequence. max_shift_steps: Maximum number of steps for a time-shift event. max_duration_steps: Maximum number of steps for a duration event. Raises: ValueError: If `num_velocity_bins` is larger than the number of MIDI velocity values. """ program, is_drum = _program_and_is_drum_from_sequence( quantized_sequence, instrument) super(NotePerformance, self).__init__( start_step=start_step, num_velocity_bins=num_velocity_bins, max_shift_steps=max_shift_steps, program=program, is_drum=is_drum) self._max_duration_steps = max_duration_steps sequences_lib.assert_is_absolute_quantized_sequence(quantized_sequence) self._steps_per_second = ( quantized_sequence.quantization_info.steps_per_second) self._events = self._from_quantized_sequence( quantized_sequence, instrument) @property def steps_per_second(self): return self._steps_per_second def set_length(self, steps, from_left=False): # This is not actually implemented, but to avoid raising exceptions during # generation just return instead of raising NotImplementedError. # TODO(fjord): Implement this. return def append(self, event): """Appends the event to the end of the sequence. Args: event: The performance event tuple to append to the end. Raises: ValueError: If `event` is not a valid performance event tuple. """ if not isinstance(event, tuple): raise ValueError('Invalid performance event tuple: %s' % event) self._events.append(event) def __str__(self): strs = [] for event in self: strs.append('TIME_SHIFT<%s>, NOTE_ON<%s>, VELOCITY<%s>, DURATION<%s>' % ( event[0].event_value, event[1].event_value, event[2].event_value, event[3].event_value)) return '\n'.join(strs) @property def num_steps(self): """Returns how many steps long this sequence is. Returns: Length of the sequence in quantized steps. """ steps = 0 for event in self._events: steps += event[0].event_value if self._events: steps += self._events[-1][3].event_value return steps @property def steps(self): """Return a Python list of the time step at each event in this sequence.""" step = self.start_step result = [] for event in self: step += event[0].event_value result.append(step) return result def _from_quantized_sequence(self, quantized_sequence, instrument): """Extract a list of events from the given quantized NoteSequence object. Within a step, new pitches are started with NOTE_ON and existing pitches are ended with NOTE_OFF. TIME_SHIFT shifts the current step forward in time. VELOCITY changes the current velocity value that will be applied to all NOTE_ON events. Args: quantized_sequence: A quantized NoteSequence instance. instrument: If not None, extract only the specified instrument. Otherwise, extract all instruments into a single event list. Returns: A list of events. Raises: TooManyTimeShiftStepsError: If the maximum number of time shift steps is exceeded. TooManyDurationStepsError: If the maximum number of duration shift steps is exceeded. """ notes = [note for note in quantized_sequence.notes if note.quantized_start_step >= self.start_step and (instrument is None or note.instrument == instrument)] sorted_notes = sorted(notes, key=lambda note: (note.start_time, note.pitch)) current_step = self.start_step performance_events = [] for note in sorted_notes: sub_events = [] # TIME_SHIFT time_shift_steps = note.quantized_start_step - current_step if time_shift_steps > self._max_shift_steps: raise TooManyTimeShiftStepsError( 'Too many steps for timeshift: %d' % time_shift_steps) else: sub_events.append( PerformanceEvent(event_type=PerformanceEvent.TIME_SHIFT, event_value=time_shift_steps)) current_step = note.quantized_start_step # NOTE_ON sub_events.append( PerformanceEvent(event_type=PerformanceEvent.NOTE_ON, event_value=note.pitch)) # VELOCITY velocity_bin = velocity_to_bin(note.velocity, self._num_velocity_bins) sub_events.append( PerformanceEvent(event_type=PerformanceEvent.VELOCITY, event_value=velocity_bin)) # DURATION duration_steps = note.quantized_end_step - note.quantized_start_step if duration_steps > self._max_duration_steps: raise TooManyDurationStepsError( 'Too many steps for duration: %s' % note) sub_events.append( PerformanceEvent(event_type=PerformanceEvent.DURATION, event_value=duration_steps)) performance_events.append(tuple(sub_events)) return performance_events def to_sequence(self, instrument=0, program=None, max_note_duration=None): """Converts the Performance to NoteSequence proto. Args: instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Not used in this implementation. Returns: A NoteSequence proto. """ seconds_per_step = 1.0 / self.steps_per_second sequence_start_time = self.start_step * seconds_per_step sequence = music_pb2.NoteSequence() sequence.ticks_per_quarter = STANDARD_PPQ step = 0 if program is None: # Use program associated with the performance (or default program). program = self.program if self.program is not None else DEFAULT_PROGRAM is_drum = self.is_drum if self.is_drum is not None else False for event in self: step += event[0].event_value note = sequence.notes.add() note.start_time = step * seconds_per_step + sequence_start_time note.end_time = ((step + event[3].event_value) * seconds_per_step + sequence_start_time) note.pitch = event[1].event_value note.velocity = velocity_bin_to_velocity( event[2].event_value, self._num_velocity_bins) note.instrument = instrument note.program = program note.is_drum = is_drum if note.end_time > sequence.total_time: sequence.total_time = note.end_time return sequence
Ancestors
Instance variables
var steps_per_second
-
Expand source code
@property def steps_per_second(self): return self._steps_per_second
Methods
def append(self, event)
-
Appends the event to the end of the sequence.
Args
event
- The performance event tuple to append to the end.
Raises
ValueError
- If
event
is not a valid performance event tuple.
Expand source code
def append(self, event): """Appends the event to the end of the sequence. Args: event: The performance event tuple to append to the end. Raises: ValueError: If `event` is not a valid performance event tuple. """ if not isinstance(event, tuple): raise ValueError('Invalid performance event tuple: %s' % event) self._events.append(event)
def to_sequence(self, instrument=0, program=None, max_note_duration=None)
-
Converts the Performance to NoteSequence proto.
Args
instrument
- MIDI instrument to give each note.
program
- MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists).
max_note_duration
- Not used in this implementation.
Returns
A NoteSequence proto.
Expand source code
def to_sequence(self, instrument=0, program=None, max_note_duration=None): """Converts the Performance to NoteSequence proto. Args: instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Not used in this implementation. Returns: A NoteSequence proto. """ seconds_per_step = 1.0 / self.steps_per_second sequence_start_time = self.start_step * seconds_per_step sequence = music_pb2.NoteSequence() sequence.ticks_per_quarter = STANDARD_PPQ step = 0 if program is None: # Use program associated with the performance (or default program). program = self.program if self.program is not None else DEFAULT_PROGRAM is_drum = self.is_drum if self.is_drum is not None else False for event in self: step += event[0].event_value note = sequence.notes.add() note.start_time = step * seconds_per_step + sequence_start_time note.end_time = ((step + event[3].event_value) * seconds_per_step + sequence_start_time) note.pitch = event[1].event_value note.velocity = velocity_bin_to_velocity( event[2].event_value, self._num_velocity_bins) note.instrument = instrument note.program = program note.is_drum = is_drum if note.end_time > sequence.total_time: sequence.total_time = note.end_time return sequence
Inherited members
class NotePerformanceError (*args, **kwargs)
-
Common base class for all non-exit exceptions.
Expand source code
class NotePerformanceError(Exception): pass
Ancestors
- builtins.Exception
- builtins.BaseException
Subclasses
class Performance (quantized_sequence=None, steps_per_second=None, start_step=0, num_velocity_bins=0, max_shift_steps=100, instrument=None, program=None, is_drum=None)
-
Performance with absolute timing and unknown meter.
Construct a Performance.
Either quantized_sequence or steps_per_second should be supplied.
Args
quantized_sequence
- A quantized NoteSequence proto.
steps_per_second
- Number of quantized time steps per second, if using absolute quantization.
start_step
- The offset of this sequence relative to the beginning of the source sequence. If a quantized sequence is used as input, only notes starting after this step will be considered.
num_velocity_bins
- Number of velocity bins to use. If 0, velocity events will not be included at all.
max_shift_steps
- Maximum number of steps for a single time-shift event.
instrument
- If not None, extract only the specified instrument from
quantized_sequence
. Otherwise, extract all instruments. program
- MIDI program used for this performance, or None if not specified.
Ignored if
quantized_sequence
is provided. is_drum
- Whether or not this performance consists of drums, or None if not
specified. Ignored if
quantized_sequence
is provided.
Raises
ValueError
- If both or neither of
quantized_sequence
orsteps_per_second
is specified.
Expand source code
class Performance(BasePerformance): """Performance with absolute timing and unknown meter.""" def __init__(self, quantized_sequence=None, steps_per_second=None, start_step=0, num_velocity_bins=0, max_shift_steps=DEFAULT_MAX_SHIFT_STEPS, instrument=None, program=None, is_drum=None): """Construct a Performance. Either quantized_sequence or steps_per_second should be supplied. Args: quantized_sequence: A quantized NoteSequence proto. steps_per_second: Number of quantized time steps per second, if using absolute quantization. start_step: The offset of this sequence relative to the beginning of the source sequence. If a quantized sequence is used as input, only notes starting after this step will be considered. num_velocity_bins: Number of velocity bins to use. If 0, velocity events will not be included at all. max_shift_steps: Maximum number of steps for a single time-shift event. instrument: If not None, extract only the specified instrument from `quantized_sequence`. Otherwise, extract all instruments. program: MIDI program used for this performance, or None if not specified. Ignored if `quantized_sequence` is provided. is_drum: Whether or not this performance consists of drums, or None if not specified. Ignored if `quantized_sequence` is provided. Raises: ValueError: If both or neither of `quantized_sequence` or `steps_per_second` is specified. """ if (quantized_sequence, steps_per_second).count(None) != 1: raise ValueError( 'Must specify exactly one of quantized_sequence or steps_per_second') if quantized_sequence: sequences_lib.assert_is_absolute_quantized_sequence(quantized_sequence) self._steps_per_second = ( quantized_sequence.quantization_info.steps_per_second) self._events = self._from_quantized_sequence( quantized_sequence, start_step, num_velocity_bins, max_shift_steps=max_shift_steps, instrument=instrument) program, is_drum = _program_and_is_drum_from_sequence( quantized_sequence, instrument) else: self._steps_per_second = steps_per_second self._events = [] super(Performance, self).__init__( start_step=start_step, num_velocity_bins=num_velocity_bins, max_shift_steps=max_shift_steps, program=program, is_drum=is_drum) @property def steps_per_second(self): return self._steps_per_second def to_sequence(self, velocity=100, instrument=0, program=None, max_note_duration=None): """Converts the Performance to NoteSequence proto. Args: velocity: MIDI velocity to give each note. Between 1 and 127 (inclusive). If the performance contains velocity events, those will be used instead. instrument: MIDI instrument to give each note. program: MIDI program to give each note, or None to use the program associated with the Performance (or the default program if none exists). max_note_duration: Maximum note duration in seconds to allow. Notes longer than this will be truncated. If None, notes can be any length. Returns: A NoteSequence proto. """ seconds_per_step = 1.0 / self.steps_per_second return self._to_sequence( seconds_per_step=seconds_per_step, velocity=velocity, instrument=instrument, program=program, max_note_duration=max_note_duration)
Ancestors
Instance variables
var steps_per_second
-
Expand source code
@property def steps_per_second(self): return self._steps_per_second
Inherited members
class PerformanceEvent (event_type, event_value)
-
Class for storing events in a performance.
Method generated by attrs for class PerformanceEvent.
Expand source code
class PerformanceEvent(object): """Class for storing events in a performance.""" event_type = attr.ib() event_value = attr.ib() # Start of a new note. NOTE_ON = 1 # End of a note. NOTE_OFF = 2 # Shift time forward. TIME_SHIFT = 3 # Change current velocity. VELOCITY = 4 # Duration of preceding NOTE_ON. # For Note-based encoding, used instead of NOTE_OFF events. DURATION = 5 @event_type.validator def _check_event(self, attribute, value): """Validate event contents.""" del attribute, value # This checks the whole object. if self.event_type in (PerformanceEvent.NOTE_ON, PerformanceEvent.NOTE_OFF): if not MIN_MIDI_PITCH <= self.event_value <= MAX_MIDI_PITCH: raise ValueError('Invalid pitch value: %s' % self.event_value) elif self.event_type == PerformanceEvent.TIME_SHIFT: if not 0 <= self.event_value: raise ValueError('Invalid time shift value: %s' % self.event_value) elif self.event_type == PerformanceEvent.DURATION: if not 1 <= self.event_value: raise ValueError('Invalid duration value: %s' % self.event_value) elif self.event_type == PerformanceEvent.VELOCITY: if not 1 <= self.event_value <= MAX_NUM_VELOCITY_BINS: raise ValueError('Invalid velocity value: %s' % self.event_value) else: raise ValueError('Invalid event type: %s' % self.event_type)
Class variables
var DURATION
var NOTE_OFF
var NOTE_ON
var TIME_SHIFT
var VELOCITY
class TooManyDurationStepsError (*args, **kwargs)
-
Common base class for all non-exit exceptions.
Expand source code
class TooManyDurationStepsError(NotePerformanceError): pass
Ancestors
- NotePerformanceError
- builtins.Exception
- builtins.BaseException
class TooManyTimeShiftStepsError (*args, **kwargs)
-
Common base class for all non-exit exceptions.
Expand source code
class TooManyTimeShiftStepsError(NotePerformanceError): pass
Ancestors
- NotePerformanceError
- builtins.Exception
- builtins.BaseException