Module note_seq.melodies_lib

Utility functions for working with melodies.

Use extract_melodies to extract monophonic melodies from a quantized NoteSequence proto.

Use Melody.to_sequence to write a melody to a NoteSequence proto. Then use midi_io.sequence_proto_to_midi_file to write that NoteSequence to a midi file.

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 melodies.

Use extract_melodies to extract monophonic melodies from a quantized
NoteSequence proto.

Use Melody.to_sequence to write a melody to a NoteSequence proto. Then use
midi_io.sequence_proto_to_midi_file to write that NoteSequence to a midi file.
"""

from note_seq import constants
from note_seq import events_lib
from note_seq import midi_io
from note_seq import sequences_lib
from note_seq.protobuf import music_pb2
import numpy as np

MELODY_NOTE_OFF = constants.MELODY_NOTE_OFF
MELODY_NO_EVENT = constants.MELODY_NO_EVENT
MIN_MELODY_EVENT = constants.MIN_MELODY_EVENT
MAX_MELODY_EVENT = constants.MAX_MELODY_EVENT
MIN_MIDI_PITCH = constants.MIN_MIDI_PITCH
MAX_MIDI_PITCH = constants.MAX_MIDI_PITCH
NOTES_PER_OCTAVE = constants.NOTES_PER_OCTAVE
DEFAULT_STEPS_PER_BAR = constants.DEFAULT_STEPS_PER_BAR
DEFAULT_STEPS_PER_QUARTER = constants.DEFAULT_STEPS_PER_QUARTER
STANDARD_PPQ = constants.STANDARD_PPQ
NOTE_KEYS = constants.NOTE_KEYS


class PolyphonicMelodyError(Exception):
  pass


class BadNoteError(Exception):
  pass


class Melody(events_lib.SimpleEventSequence):
  """Stores a quantized stream of monophonic melody events.

  Melody is an intermediate representation that all melody models can use.
  Quantized sequence to Melody code will do work to align notes and extract
  extract monophonic melodies. Model-specific code then needs to convert Melody
  to SequenceExample protos for TensorFlow.

  Melody implements an iterable object. Simply iterate to retrieve the melody
  events.

  Melody events are integers in range [-2, 127] (inclusive), where negative
  values are the special event events: MELODY_NOTE_OFF, and MELODY_NO_EVENT.
  Non-negative values [0, 127] are note-on events for that midi pitch. A note
  starts at a non-negative value (that is the pitch), and is held through
  subsequent MELODY_NO_EVENT events until either another non-negative value is
  reached (even if the pitch is the same as the previous note), or a
  MELODY_NOTE_OFF event is reached. A MELODY_NOTE_OFF starts at least one step
  of silence, which continues through MELODY_NO_EVENT events until the next
  non-negative value.

  MELODY_NO_EVENT values are treated as default filler. Notes must be inserted
  in ascending order by start time. Note end times will be truncated if the next
  note overlaps.

  Any sustained notes are implicitly turned off at the end of a melody.

  Melodies can start at any non-negative time, and are shifted left so that
  the bar containing the first note-on event is the first bar.

  Attributes:
    start_step: The offset of the first step of the melody relative to the
        beginning of the source sequence. Will always be the first step of a
        bar.
    end_step: The offset to the beginning of the bar following the last step
       of the melody relative 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, events=None, **kwargs):
    """Construct a Melody."""
    if 'pad_event' in kwargs:
      del kwargs['pad_event']
    super(Melody, self).__init__(pad_event=MELODY_NO_EVENT,
                                 events=events, **kwargs)

  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 Melody events to set melody to.
      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.

    Raises:
      ValueError: If `events` contains an event that is not in the proper range.
    """
    for event in events:
      if not MIN_MELODY_EVENT <= event <= MAX_MELODY_EVENT:
        raise ValueError('Melody event out of range: %d' % event)
    # Replace MELODY_NOTE_OFF events with MELODY_NO_EVENT before first note.
    cleaned_events = list(events)
    for i, e in enumerate(events):
      if e not in (MELODY_NO_EVENT, MELODY_NOTE_OFF):
        break
      cleaned_events[i] = MELODY_NO_EVENT

    super(Melody, self)._from_event_list(
        cleaned_events, start_step=start_step, steps_per_bar=steps_per_bar,
        steps_per_quarter=steps_per_quarter)

  def _add_note(self, pitch, start_step, end_step):
    """Adds the given note to the `events` list.

    `start_step` is set to the given pitch. `end_step` is set to NOTE_OFF.
    Everything after `start_step` in `events` is deleted before the note is
    added. `events`'s length will be changed so that the last event has index
    `end_step`.

    Args:
      pitch: Midi pitch. An integer between 0 and 127 inclusive.
      start_step: A non-negative integer step that the note begins on.
      end_step: An integer step that the note ends on. The note is considered to
          end at the onset of the end step. `end_step` must be greater than
          `start_step`.

    Raises:
      BadNoteError: If `start_step` does not precede `end_step`.
    """
    if start_step >= end_step:
      raise BadNoteError(
          'Start step does not precede end step: start=%d, end=%d' %
          (start_step, end_step))

    self.set_length(end_step + 1)

    self._events[start_step] = pitch
    self._events[end_step] = MELODY_NOTE_OFF
    for i in range(start_step + 1, end_step):
      self._events[i] = MELODY_NO_EVENT

  def _get_last_on_off_events(self):
    """Returns indexes of the most recent pitch and NOTE_OFF events.

    Returns:
      A tuple (start_step, end_step) of the last note's on and off event
          indices.

    Raises:
      ValueError: If `events` contains no NOTE_OFF or pitch events.
    """
    last_off = len(self)
    for i in range(len(self) - 1, -1, -1):
      if self._events[i] == MELODY_NOTE_OFF:
        last_off = i
      if self._events[i] >= MIN_MIDI_PITCH:
        return (i, last_off)
    raise ValueError('No events in the stream')

  def get_note_histogram(self):
    """Gets a histogram of the note occurrences in a melody.

    Returns:
      A list of 12 ints, one for each note value (C at index 0 through B at
      index 11). Each int is the total number of times that note occurred in
      the melody.
    """
    np_melody = np.array(self._events, dtype=int)
    return np.bincount(np_melody[np_melody >= MIN_MIDI_PITCH] %
                       NOTES_PER_OCTAVE,
                       minlength=NOTES_PER_OCTAVE)

  def get_major_key_histogram(self):
    """Gets a histogram of the how many notes fit into each key.

    Returns:
      A list of 12 ints, one for each Major key (C Major at index 0 through
      B Major at index 11). Each int is the total number of notes that could
      fit into that key.
    """
    note_histogram = self.get_note_histogram()
    key_histogram = np.zeros(NOTES_PER_OCTAVE)
    for note, count in enumerate(note_histogram):
      key_histogram[NOTE_KEYS[note]] += count
    return key_histogram

  def get_major_key(self):
    """Finds the major key that this melody most likely belongs to.

    If multiple keys match equally, the key with the lowest index is returned,
    where the indexes of the keys are C Major = 0 through B Major = 11.

    Returns:
      An int for the most likely key (C Major = 0 through B Major = 11)
    """
    key_histogram = self.get_major_key_histogram()
    return key_histogram.argmax()

  def append(self, event):
    """Appends the event to the end of the melody and increments the end step.

    An implicit NOTE_OFF at the end of the melody will not be respected by this
    modification.

    Args:
      event: The integer Melody event to append to the end.
    Raises:
      ValueError: If `event` is not in the proper range.
    """
    if not MIN_MELODY_EVENT <= event <= MAX_MELODY_EVENT:
      raise ValueError('Event out of range: %d' % event)
    super(Melody, self).append(event)

  def from_quantized_sequence(self,
                              quantized_sequence,
                              search_start_step=0,
                              instrument=0,
                              gap_bars=1,
                              ignore_polyphonic_notes=False,
                              pad_end=False,
                              filter_drums=True):
    """Populate self with a melody from the given quantized NoteSequence.

    A monophonic melody is extracted from the given `instrument` starting at
    `search_start_step`. `instrument` and `search_start_step` can be used to
    drive extraction of multiple melodies from the same quantized sequence. The
    end step of the extracted melody will be stored in `self._end_step`.

    0 velocity notes are ignored. The melody extraction is ended when there are
    no held notes for a time stretch of `gap_bars` in bars (measures) of music.
    The number of time steps per bar is computed from the time signature in
    `quantized_sequence`.

    `ignore_polyphonic_notes` determines what happens when polyphonic (multiple
    notes start at the same time) data is encountered. If
    `ignore_polyphonic_notes` is true, the highest pitch is used in the melody
    when multiple notes start at the same time. If false, an exception is
    raised.

    Args:
      quantized_sequence: A NoteSequence quantized with
          sequences_lib.quantize_note_sequence.
      search_start_step: Start searching for a melody at this time step. Assumed
          to be the first step of a bar.
      instrument: Search for a melody in this instrument number.
      gap_bars: If this many bars or more follow a NOTE_OFF event, the melody
          is ended.
      ignore_polyphonic_notes: If True, the highest pitch is used in the melody
          when multiple notes start at the same time. If False,
          PolyphonicMelodyError will be raised if multiple notes start at
          the same time.
      pad_end: If True, the end of the melody will be padded with NO_EVENTs so
          that it will end at a bar boundary.
      filter_drums: If True, notes for which `is_drum` is True will be ignored.

    Raises:
      NonIntegerStepsPerBarError: If `quantized_sequence`'s bar length
          (derived from its time signature) is not an integer number of time
          steps.
      PolyphonicMelodyError: If any of the notes start on the same step
          and `ignore_polyphonic_notes` is False.
    """
    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)
    self._reset()

    steps_per_bar_float = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)
    if steps_per_bar_float % 1 != 0:
      raise events_lib.NonIntegerStepsPerBarError(
          'There are %f timesteps per bar. Time signature: %d/%d' %
          (steps_per_bar_float, quantized_sequence.time_signatures[0].numerator,
           quantized_sequence.time_signatures[0].denominator))
    self._steps_per_bar = steps_per_bar = int(steps_per_bar_float)
    self._steps_per_quarter = (
        quantized_sequence.quantization_info.steps_per_quarter)

    # Sort track by note start times, and secondarily by pitch descending.
    notes = sorted([n for n in quantized_sequence.notes
                    if n.instrument == instrument and
                    n.quantized_start_step >= search_start_step],
                   key=lambda note: (note.quantized_start_step, -note.pitch))

    if not notes:
      return

    # The first step in the melody, beginning at the first step of a bar.
    melody_start_step = (
        notes[0].quantized_start_step -
        (notes[0].quantized_start_step - search_start_step) % steps_per_bar)
    for note in notes:
      if filter_drums and note.is_drum:
        continue

      # Ignore 0 velocity notes.
      if not note.velocity:
        continue

      start_index = note.quantized_start_step - melody_start_step
      end_index = note.quantized_end_step - melody_start_step

      if not self._events:
        # If there are no events, we don't need to check for polyphony.
        self._add_note(note.pitch, start_index, end_index)
        continue

      # If `start_index` comes before or lands on an already added note's start
      # step, we cannot add it. In that case either discard the melody or keep
      # the highest pitch.
      last_on, last_off = self._get_last_on_off_events()
      on_distance = start_index - last_on
      off_distance = start_index - last_off
      if on_distance == 0:
        if ignore_polyphonic_notes:
          # Keep highest note.
          # Notes are sorted by pitch descending, so if a note is already at
          # this position its the highest pitch.
          continue
        else:
          self._reset()
          raise PolyphonicMelodyError()
      elif on_distance < 0:
        raise PolyphonicMelodyError(
            'Unexpected note. Not in ascending order.')

      # If a gap of `gap` or more steps is found, end the melody.
      if len(self) and off_distance >= gap_bars * steps_per_bar:  # pylint:disable=len-as-condition
        break

      # Add the note-on and off events to the melody.
      self._add_note(note.pitch, start_index, end_index)

    if not self._events:
      # If no notes were added, don't set `_start_step` and `_end_step`.
      return

    self._start_step = melody_start_step

    # Strip final MELODY_NOTE_OFF event.
    if self._events[-1] == MELODY_NOTE_OFF:
      del self._events[-1]

    length = len(self)
    # Optionally round up `_end_step` to a multiple of `steps_per_bar`.
    if pad_end:
      length += -len(self) % steps_per_bar
    self.set_length(length)

  def to_sequence(self,
                  velocity=100,
                  instrument=0,
                  program=0,
                  sequence_start_time=0.0,
                  qpm=120.0):
    """Converts the Melody to NoteSequence proto.

    The end of the melody is treated as a NOTE_OFF event for any sustained
    notes.

    Args:
      velocity: Midi velocity to give each note. Between 1 and 127 (inclusive).
      instrument: Midi instrument to give each note.
      program: Midi program to give each note.
      sequence_start_time: A time in seconds (float) that the first note in the
          sequence will land on.
      qpm: Quarter notes per minute (float).

    Returns:
      A NoteSequence proto encoding the given melody.
    """
    seconds_per_step = 60.0 / qpm / self.steps_per_quarter

    sequence = music_pb2.NoteSequence()
    sequence.tempos.add().qpm = qpm
    sequence.ticks_per_quarter = STANDARD_PPQ

    sequence_start_time += self.start_step * seconds_per_step
    current_sequence_note = None
    for step, note in enumerate(self):
      if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH:
        # End any sustained notes.
        if current_sequence_note is not None:
          current_sequence_note.end_time = (
              step * seconds_per_step + sequence_start_time)

        # Add a note.
        current_sequence_note = sequence.notes.add()
        current_sequence_note.start_time = (
            step * seconds_per_step + sequence_start_time)
        current_sequence_note.pitch = note
        current_sequence_note.velocity = velocity
        current_sequence_note.instrument = instrument
        current_sequence_note.program = program

      elif note == MELODY_NOTE_OFF:
        # End any sustained notes.
        if current_sequence_note is not None:
          current_sequence_note.end_time = (
              step * seconds_per_step + sequence_start_time)
          current_sequence_note = None

    # End any sustained notes.
    if current_sequence_note is not None:
      current_sequence_note.end_time = (
          len(self) * seconds_per_step + sequence_start_time)

    if sequence.notes:
      sequence.total_time = sequence.notes[-1].end_time

    return sequence

  def transpose(self, transpose_amount, min_note=0, max_note=128):
    """Transpose notes in this Melody.

    All notes are transposed the specified amount. Additionally, all notes
    are octave shifted to lie within the [min_note, max_note) range.

    Args:
      transpose_amount: The number of half steps to transpose this Melody.
          Positive values transpose up. Negative values transpose down.
      min_note: Minimum pitch (inclusive) that the resulting notes will take on.
      max_note: Maximum pitch (exclusive) that the resulting notes will take on.
    """
    for i in range(len(self)):
      # Transpose MIDI pitches. Special events below MIN_MIDI_PITCH are not
      # changed.
      if self._events[i] >= MIN_MIDI_PITCH:
        self._events[i] += transpose_amount
        if self._events[i] < min_note:
          self._events[i] = (
              min_note + (self._events[i] - min_note) % NOTES_PER_OCTAVE)
        elif self._events[i] >= max_note:
          self._events[i] = (max_note - NOTES_PER_OCTAVE +
                             (self._events[i] - max_note) % NOTES_PER_OCTAVE)

  def squash(self, min_note, max_note, transpose_to_key=None):
    """Transpose and octave shift the notes in this Melody.

    The key center of this melody is computed with a heuristic, and the notes
    are transposed to be in the given key. The melody is also octave shifted
    to be centered in the given range. Additionally, all notes are octave
    shifted to lie within a given range.

    Args:
      min_note: Minimum pitch (inclusive) that the resulting notes will take on.
      max_note: Maximum pitch (exclusive) that the resulting notes will take on.
      transpose_to_key: The melody is transposed to be in this key or None if
         should not be transposed. 0 = C Major.

    Returns:
      How much notes are transposed by.
    """
    if transpose_to_key is None:
      transpose_amount = 0
    else:
      melody_key = self.get_major_key()
      key_diff = transpose_to_key - melody_key
      midi_notes = [note for note in self._events
                    if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH]
      if not midi_notes:
        return 0
      melody_min_note = min(midi_notes)
      melody_max_note = max(midi_notes)
      melody_center = (melody_min_note + melody_max_note) / 2
      target_center = (min_note + max_note - 1) / 2
      center_diff = target_center - (melody_center + key_diff)
      transpose_amount = (
          key_diff +
          NOTES_PER_OCTAVE * int(round(center_diff / float(NOTES_PER_OCTAVE))))
    self.transpose(transpose_amount, min_note, max_note)

    return transpose_amount

  def set_length(self, steps, from_left=False):
    """Sets the length of the melody to the specified number of steps.

    If the melody is not long enough, ends any sustained notes and adds NO_EVENT
    steps for padding. If it is too long, it will be truncated to the requested
    length.

    Args:
      steps: How many steps long the melody should be.
      from_left: Whether to add/remove from the left instead of right.
    """
    old_len = len(self)
    super(Melody, self).set_length(steps, from_left=from_left)
    if steps > old_len and not from_left:
      # When extending the melody on the right, we end any sustained notes.
      for i in reversed(range(old_len)):
        if self._events[i] == MELODY_NOTE_OFF:
          break
        elif self._events[i] != MELODY_NO_EVENT:
          self._events[old_len] = MELODY_NOTE_OFF
          break

  def increase_resolution(self, k):
    """Increase the resolution of a Melody.

    Increases the resolution of a Melody object by a factor of `k`. This uses
    MELODY_NO_EVENT to extend each event in the melody to be `k` steps long.

    Args:
      k: An integer, the factor by which to increase the resolution of the
          melody.
    """
    super(Melody, self).increase_resolution(
        k, fill_event=MELODY_NO_EVENT)


def midi_file_to_melody(midi_file, steps_per_quarter=4, qpm=None,
                        ignore_polyphonic_notes=True):
  """Loads a melody from a MIDI file.

  Args:
    midi_file: Absolute path to MIDI file.
    steps_per_quarter: Quantization of Melody. For example, 4 = 16th notes.
    qpm: Tempo in quarters per a minute. If not set, tries to use the first
        tempo of the midi track and defaults to
        note_seq.DEFAULT_QUARTERS_PER_MINUTE if fails.
    ignore_polyphonic_notes: Only use the highest simultaneous note if True.

  Returns:
    A Melody object extracted from the MIDI file.
  """
  sequence = midi_io.midi_file_to_sequence_proto(midi_file)
  if qpm is None:
    if sequence.tempos:
      qpm = sequence.tempos[0].qpm
    else:
      qpm = constants.DEFAULT_QUARTERS_PER_MINUTE
  quantized_sequence = sequences_lib.quantize_note_sequence(
      sequence, steps_per_quarter=steps_per_quarter)
  melody = Melody()
  melody.from_quantized_sequence(
      quantized_sequence, ignore_polyphonic_notes=ignore_polyphonic_notes)
  return melody

Functions

def midi_file_to_melody(midi_file, steps_per_quarter=4, qpm=None, ignore_polyphonic_notes=True)

Loads a melody from a MIDI file.

Args

midi_file
Absolute path to MIDI file.
steps_per_quarter
Quantization of Melody. For example, 4 = 16th notes.
qpm
Tempo in quarters per a minute. If not set, tries to use the first tempo of the midi track and defaults to note_seq.DEFAULT_QUARTERS_PER_MINUTE if fails.
ignore_polyphonic_notes
Only use the highest simultaneous note if True.

Returns

A Melody object extracted from the MIDI file.

Expand source code
def midi_file_to_melody(midi_file, steps_per_quarter=4, qpm=None,
                        ignore_polyphonic_notes=True):
  """Loads a melody from a MIDI file.

  Args:
    midi_file: Absolute path to MIDI file.
    steps_per_quarter: Quantization of Melody. For example, 4 = 16th notes.
    qpm: Tempo in quarters per a minute. If not set, tries to use the first
        tempo of the midi track and defaults to
        note_seq.DEFAULT_QUARTERS_PER_MINUTE if fails.
    ignore_polyphonic_notes: Only use the highest simultaneous note if True.

  Returns:
    A Melody object extracted from the MIDI file.
  """
  sequence = midi_io.midi_file_to_sequence_proto(midi_file)
  if qpm is None:
    if sequence.tempos:
      qpm = sequence.tempos[0].qpm
    else:
      qpm = constants.DEFAULT_QUARTERS_PER_MINUTE
  quantized_sequence = sequences_lib.quantize_note_sequence(
      sequence, steps_per_quarter=steps_per_quarter)
  melody = Melody()
  melody.from_quantized_sequence(
      quantized_sequence, ignore_polyphonic_notes=ignore_polyphonic_notes)
  return melody

Classes

class BadNoteError (*args, **kwargs)

Common base class for all non-exit exceptions.

Expand source code
class BadNoteError(Exception):
  pass

Ancestors

  • builtins.Exception
  • builtins.BaseException
class Melody (events=None, **kwargs)

Stores a quantized stream of monophonic melody events.

Melody is an intermediate representation that all melody models can use. Quantized sequence to Melody code will do work to align notes and extract extract monophonic melodies. Model-specific code then needs to convert Melody to SequenceExample protos for TensorFlow.

Melody implements an iterable object. Simply iterate to retrieve the melody events.

Melody events are integers in range [-2, 127] (inclusive), where negative values are the special event events: MELODY_NOTE_OFF, and MELODY_NO_EVENT. Non-negative values [0, 127] are note-on events for that midi pitch. A note starts at a non-negative value (that is the pitch), and is held through subsequent MELODY_NO_EVENT events until either another non-negative value is reached (even if the pitch is the same as the previous note), or a MELODY_NOTE_OFF event is reached. A MELODY_NOTE_OFF starts at least one step of silence, which continues through MELODY_NO_EVENT events until the next non-negative value.

MELODY_NO_EVENT values are treated as default filler. Notes must be inserted in ascending order by start time. Note end times will be truncated if the next note overlaps.

Any sustained notes are implicitly turned off at the end of a melody.

Melodies can start at any non-negative time, and are shifted left so that the bar containing the first note-on event is the first bar.

Attributes

start_step
The offset of the first step of the melody relative to the beginning of the source sequence. Will always be the first step of a bar.
end_step
The offset to the beginning of the bar following the last step of the melody relative 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.

Construct a Melody.

Expand source code
class Melody(events_lib.SimpleEventSequence):
  """Stores a quantized stream of monophonic melody events.

  Melody is an intermediate representation that all melody models can use.
  Quantized sequence to Melody code will do work to align notes and extract
  extract monophonic melodies. Model-specific code then needs to convert Melody
  to SequenceExample protos for TensorFlow.

  Melody implements an iterable object. Simply iterate to retrieve the melody
  events.

  Melody events are integers in range [-2, 127] (inclusive), where negative
  values are the special event events: MELODY_NOTE_OFF, and MELODY_NO_EVENT.
  Non-negative values [0, 127] are note-on events for that midi pitch. A note
  starts at a non-negative value (that is the pitch), and is held through
  subsequent MELODY_NO_EVENT events until either another non-negative value is
  reached (even if the pitch is the same as the previous note), or a
  MELODY_NOTE_OFF event is reached. A MELODY_NOTE_OFF starts at least one step
  of silence, which continues through MELODY_NO_EVENT events until the next
  non-negative value.

  MELODY_NO_EVENT values are treated as default filler. Notes must be inserted
  in ascending order by start time. Note end times will be truncated if the next
  note overlaps.

  Any sustained notes are implicitly turned off at the end of a melody.

  Melodies can start at any non-negative time, and are shifted left so that
  the bar containing the first note-on event is the first bar.

  Attributes:
    start_step: The offset of the first step of the melody relative to the
        beginning of the source sequence. Will always be the first step of a
        bar.
    end_step: The offset to the beginning of the bar following the last step
       of the melody relative 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, events=None, **kwargs):
    """Construct a Melody."""
    if 'pad_event' in kwargs:
      del kwargs['pad_event']
    super(Melody, self).__init__(pad_event=MELODY_NO_EVENT,
                                 events=events, **kwargs)

  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 Melody events to set melody to.
      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.

    Raises:
      ValueError: If `events` contains an event that is not in the proper range.
    """
    for event in events:
      if not MIN_MELODY_EVENT <= event <= MAX_MELODY_EVENT:
        raise ValueError('Melody event out of range: %d' % event)
    # Replace MELODY_NOTE_OFF events with MELODY_NO_EVENT before first note.
    cleaned_events = list(events)
    for i, e in enumerate(events):
      if e not in (MELODY_NO_EVENT, MELODY_NOTE_OFF):
        break
      cleaned_events[i] = MELODY_NO_EVENT

    super(Melody, self)._from_event_list(
        cleaned_events, start_step=start_step, steps_per_bar=steps_per_bar,
        steps_per_quarter=steps_per_quarter)

  def _add_note(self, pitch, start_step, end_step):
    """Adds the given note to the `events` list.

    `start_step` is set to the given pitch. `end_step` is set to NOTE_OFF.
    Everything after `start_step` in `events` is deleted before the note is
    added. `events`'s length will be changed so that the last event has index
    `end_step`.

    Args:
      pitch: Midi pitch. An integer between 0 and 127 inclusive.
      start_step: A non-negative integer step that the note begins on.
      end_step: An integer step that the note ends on. The note is considered to
          end at the onset of the end step. `end_step` must be greater than
          `start_step`.

    Raises:
      BadNoteError: If `start_step` does not precede `end_step`.
    """
    if start_step >= end_step:
      raise BadNoteError(
          'Start step does not precede end step: start=%d, end=%d' %
          (start_step, end_step))

    self.set_length(end_step + 1)

    self._events[start_step] = pitch
    self._events[end_step] = MELODY_NOTE_OFF
    for i in range(start_step + 1, end_step):
      self._events[i] = MELODY_NO_EVENT

  def _get_last_on_off_events(self):
    """Returns indexes of the most recent pitch and NOTE_OFF events.

    Returns:
      A tuple (start_step, end_step) of the last note's on and off event
          indices.

    Raises:
      ValueError: If `events` contains no NOTE_OFF or pitch events.
    """
    last_off = len(self)
    for i in range(len(self) - 1, -1, -1):
      if self._events[i] == MELODY_NOTE_OFF:
        last_off = i
      if self._events[i] >= MIN_MIDI_PITCH:
        return (i, last_off)
    raise ValueError('No events in the stream')

  def get_note_histogram(self):
    """Gets a histogram of the note occurrences in a melody.

    Returns:
      A list of 12 ints, one for each note value (C at index 0 through B at
      index 11). Each int is the total number of times that note occurred in
      the melody.
    """
    np_melody = np.array(self._events, dtype=int)
    return np.bincount(np_melody[np_melody >= MIN_MIDI_PITCH] %
                       NOTES_PER_OCTAVE,
                       minlength=NOTES_PER_OCTAVE)

  def get_major_key_histogram(self):
    """Gets a histogram of the how many notes fit into each key.

    Returns:
      A list of 12 ints, one for each Major key (C Major at index 0 through
      B Major at index 11). Each int is the total number of notes that could
      fit into that key.
    """
    note_histogram = self.get_note_histogram()
    key_histogram = np.zeros(NOTES_PER_OCTAVE)
    for note, count in enumerate(note_histogram):
      key_histogram[NOTE_KEYS[note]] += count
    return key_histogram

  def get_major_key(self):
    """Finds the major key that this melody most likely belongs to.

    If multiple keys match equally, the key with the lowest index is returned,
    where the indexes of the keys are C Major = 0 through B Major = 11.

    Returns:
      An int for the most likely key (C Major = 0 through B Major = 11)
    """
    key_histogram = self.get_major_key_histogram()
    return key_histogram.argmax()

  def append(self, event):
    """Appends the event to the end of the melody and increments the end step.

    An implicit NOTE_OFF at the end of the melody will not be respected by this
    modification.

    Args:
      event: The integer Melody event to append to the end.
    Raises:
      ValueError: If `event` is not in the proper range.
    """
    if not MIN_MELODY_EVENT <= event <= MAX_MELODY_EVENT:
      raise ValueError('Event out of range: %d' % event)
    super(Melody, self).append(event)

  def from_quantized_sequence(self,
                              quantized_sequence,
                              search_start_step=0,
                              instrument=0,
                              gap_bars=1,
                              ignore_polyphonic_notes=False,
                              pad_end=False,
                              filter_drums=True):
    """Populate self with a melody from the given quantized NoteSequence.

    A monophonic melody is extracted from the given `instrument` starting at
    `search_start_step`. `instrument` and `search_start_step` can be used to
    drive extraction of multiple melodies from the same quantized sequence. The
    end step of the extracted melody will be stored in `self._end_step`.

    0 velocity notes are ignored. The melody extraction is ended when there are
    no held notes for a time stretch of `gap_bars` in bars (measures) of music.
    The number of time steps per bar is computed from the time signature in
    `quantized_sequence`.

    `ignore_polyphonic_notes` determines what happens when polyphonic (multiple
    notes start at the same time) data is encountered. If
    `ignore_polyphonic_notes` is true, the highest pitch is used in the melody
    when multiple notes start at the same time. If false, an exception is
    raised.

    Args:
      quantized_sequence: A NoteSequence quantized with
          sequences_lib.quantize_note_sequence.
      search_start_step: Start searching for a melody at this time step. Assumed
          to be the first step of a bar.
      instrument: Search for a melody in this instrument number.
      gap_bars: If this many bars or more follow a NOTE_OFF event, the melody
          is ended.
      ignore_polyphonic_notes: If True, the highest pitch is used in the melody
          when multiple notes start at the same time. If False,
          PolyphonicMelodyError will be raised if multiple notes start at
          the same time.
      pad_end: If True, the end of the melody will be padded with NO_EVENTs so
          that it will end at a bar boundary.
      filter_drums: If True, notes for which `is_drum` is True will be ignored.

    Raises:
      NonIntegerStepsPerBarError: If `quantized_sequence`'s bar length
          (derived from its time signature) is not an integer number of time
          steps.
      PolyphonicMelodyError: If any of the notes start on the same step
          and `ignore_polyphonic_notes` is False.
    """
    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)
    self._reset()

    steps_per_bar_float = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)
    if steps_per_bar_float % 1 != 0:
      raise events_lib.NonIntegerStepsPerBarError(
          'There are %f timesteps per bar. Time signature: %d/%d' %
          (steps_per_bar_float, quantized_sequence.time_signatures[0].numerator,
           quantized_sequence.time_signatures[0].denominator))
    self._steps_per_bar = steps_per_bar = int(steps_per_bar_float)
    self._steps_per_quarter = (
        quantized_sequence.quantization_info.steps_per_quarter)

    # Sort track by note start times, and secondarily by pitch descending.
    notes = sorted([n for n in quantized_sequence.notes
                    if n.instrument == instrument and
                    n.quantized_start_step >= search_start_step],
                   key=lambda note: (note.quantized_start_step, -note.pitch))

    if not notes:
      return

    # The first step in the melody, beginning at the first step of a bar.
    melody_start_step = (
        notes[0].quantized_start_step -
        (notes[0].quantized_start_step - search_start_step) % steps_per_bar)
    for note in notes:
      if filter_drums and note.is_drum:
        continue

      # Ignore 0 velocity notes.
      if not note.velocity:
        continue

      start_index = note.quantized_start_step - melody_start_step
      end_index = note.quantized_end_step - melody_start_step

      if not self._events:
        # If there are no events, we don't need to check for polyphony.
        self._add_note(note.pitch, start_index, end_index)
        continue

      # If `start_index` comes before or lands on an already added note's start
      # step, we cannot add it. In that case either discard the melody or keep
      # the highest pitch.
      last_on, last_off = self._get_last_on_off_events()
      on_distance = start_index - last_on
      off_distance = start_index - last_off
      if on_distance == 0:
        if ignore_polyphonic_notes:
          # Keep highest note.
          # Notes are sorted by pitch descending, so if a note is already at
          # this position its the highest pitch.
          continue
        else:
          self._reset()
          raise PolyphonicMelodyError()
      elif on_distance < 0:
        raise PolyphonicMelodyError(
            'Unexpected note. Not in ascending order.')

      # If a gap of `gap` or more steps is found, end the melody.
      if len(self) and off_distance >= gap_bars * steps_per_bar:  # pylint:disable=len-as-condition
        break

      # Add the note-on and off events to the melody.
      self._add_note(note.pitch, start_index, end_index)

    if not self._events:
      # If no notes were added, don't set `_start_step` and `_end_step`.
      return

    self._start_step = melody_start_step

    # Strip final MELODY_NOTE_OFF event.
    if self._events[-1] == MELODY_NOTE_OFF:
      del self._events[-1]

    length = len(self)
    # Optionally round up `_end_step` to a multiple of `steps_per_bar`.
    if pad_end:
      length += -len(self) % steps_per_bar
    self.set_length(length)

  def to_sequence(self,
                  velocity=100,
                  instrument=0,
                  program=0,
                  sequence_start_time=0.0,
                  qpm=120.0):
    """Converts the Melody to NoteSequence proto.

    The end of the melody is treated as a NOTE_OFF event for any sustained
    notes.

    Args:
      velocity: Midi velocity to give each note. Between 1 and 127 (inclusive).
      instrument: Midi instrument to give each note.
      program: Midi program to give each note.
      sequence_start_time: A time in seconds (float) that the first note in the
          sequence will land on.
      qpm: Quarter notes per minute (float).

    Returns:
      A NoteSequence proto encoding the given melody.
    """
    seconds_per_step = 60.0 / qpm / self.steps_per_quarter

    sequence = music_pb2.NoteSequence()
    sequence.tempos.add().qpm = qpm
    sequence.ticks_per_quarter = STANDARD_PPQ

    sequence_start_time += self.start_step * seconds_per_step
    current_sequence_note = None
    for step, note in enumerate(self):
      if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH:
        # End any sustained notes.
        if current_sequence_note is not None:
          current_sequence_note.end_time = (
              step * seconds_per_step + sequence_start_time)

        # Add a note.
        current_sequence_note = sequence.notes.add()
        current_sequence_note.start_time = (
            step * seconds_per_step + sequence_start_time)
        current_sequence_note.pitch = note
        current_sequence_note.velocity = velocity
        current_sequence_note.instrument = instrument
        current_sequence_note.program = program

      elif note == MELODY_NOTE_OFF:
        # End any sustained notes.
        if current_sequence_note is not None:
          current_sequence_note.end_time = (
              step * seconds_per_step + sequence_start_time)
          current_sequence_note = None

    # End any sustained notes.
    if current_sequence_note is not None:
      current_sequence_note.end_time = (
          len(self) * seconds_per_step + sequence_start_time)

    if sequence.notes:
      sequence.total_time = sequence.notes[-1].end_time

    return sequence

  def transpose(self, transpose_amount, min_note=0, max_note=128):
    """Transpose notes in this Melody.

    All notes are transposed the specified amount. Additionally, all notes
    are octave shifted to lie within the [min_note, max_note) range.

    Args:
      transpose_amount: The number of half steps to transpose this Melody.
          Positive values transpose up. Negative values transpose down.
      min_note: Minimum pitch (inclusive) that the resulting notes will take on.
      max_note: Maximum pitch (exclusive) that the resulting notes will take on.
    """
    for i in range(len(self)):
      # Transpose MIDI pitches. Special events below MIN_MIDI_PITCH are not
      # changed.
      if self._events[i] >= MIN_MIDI_PITCH:
        self._events[i] += transpose_amount
        if self._events[i] < min_note:
          self._events[i] = (
              min_note + (self._events[i] - min_note) % NOTES_PER_OCTAVE)
        elif self._events[i] >= max_note:
          self._events[i] = (max_note - NOTES_PER_OCTAVE +
                             (self._events[i] - max_note) % NOTES_PER_OCTAVE)

  def squash(self, min_note, max_note, transpose_to_key=None):
    """Transpose and octave shift the notes in this Melody.

    The key center of this melody is computed with a heuristic, and the notes
    are transposed to be in the given key. The melody is also octave shifted
    to be centered in the given range. Additionally, all notes are octave
    shifted to lie within a given range.

    Args:
      min_note: Minimum pitch (inclusive) that the resulting notes will take on.
      max_note: Maximum pitch (exclusive) that the resulting notes will take on.
      transpose_to_key: The melody is transposed to be in this key or None if
         should not be transposed. 0 = C Major.

    Returns:
      How much notes are transposed by.
    """
    if transpose_to_key is None:
      transpose_amount = 0
    else:
      melody_key = self.get_major_key()
      key_diff = transpose_to_key - melody_key
      midi_notes = [note for note in self._events
                    if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH]
      if not midi_notes:
        return 0
      melody_min_note = min(midi_notes)
      melody_max_note = max(midi_notes)
      melody_center = (melody_min_note + melody_max_note) / 2
      target_center = (min_note + max_note - 1) / 2
      center_diff = target_center - (melody_center + key_diff)
      transpose_amount = (
          key_diff +
          NOTES_PER_OCTAVE * int(round(center_diff / float(NOTES_PER_OCTAVE))))
    self.transpose(transpose_amount, min_note, max_note)

    return transpose_amount

  def set_length(self, steps, from_left=False):
    """Sets the length of the melody to the specified number of steps.

    If the melody is not long enough, ends any sustained notes and adds NO_EVENT
    steps for padding. If it is too long, it will be truncated to the requested
    length.

    Args:
      steps: How many steps long the melody should be.
      from_left: Whether to add/remove from the left instead of right.
    """
    old_len = len(self)
    super(Melody, self).set_length(steps, from_left=from_left)
    if steps > old_len and not from_left:
      # When extending the melody on the right, we end any sustained notes.
      for i in reversed(range(old_len)):
        if self._events[i] == MELODY_NOTE_OFF:
          break
        elif self._events[i] != MELODY_NO_EVENT:
          self._events[old_len] = MELODY_NOTE_OFF
          break

  def increase_resolution(self, k):
    """Increase the resolution of a Melody.

    Increases the resolution of a Melody object by a factor of `k`. This uses
    MELODY_NO_EVENT to extend each event in the melody to be `k` steps long.

    Args:
      k: An integer, the factor by which to increase the resolution of the
          melody.
    """
    super(Melody, self).increase_resolution(
        k, fill_event=MELODY_NO_EVENT)

Ancestors

Methods

def append(self, event)

Appends the event to the end of the melody and increments the end step.

An implicit NOTE_OFF at the end of the melody will not be respected by this modification.

Args

event
The integer Melody event to append to the end.

Raises

ValueError
If event is not in the proper range.
Expand source code
def append(self, event):
  """Appends the event to the end of the melody and increments the end step.

  An implicit NOTE_OFF at the end of the melody will not be respected by this
  modification.

  Args:
    event: The integer Melody event to append to the end.
  Raises:
    ValueError: If `event` is not in the proper range.
  """
  if not MIN_MELODY_EVENT <= event <= MAX_MELODY_EVENT:
    raise ValueError('Event out of range: %d' % event)
  super(Melody, self).append(event)
def from_quantized_sequence(self, quantized_sequence, search_start_step=0, instrument=0, gap_bars=1, ignore_polyphonic_notes=False, pad_end=False, filter_drums=True)

Populate self with a melody from the given quantized NoteSequence.

A monophonic melody is extracted from the given instrument starting at search_start_step. instrument and search_start_step can be used to drive extraction of multiple melodies from the same quantized sequence. The end step of the extracted melody will be stored in self._end_step.

0 velocity notes are ignored. The melody extraction is ended when there are no held notes for a time stretch of gap_bars in bars (measures) of music. The number of time steps per bar is computed from the time signature in quantized_sequence.

ignore_polyphonic_notes determines what happens when polyphonic (multiple notes start at the same time) data is encountered. If ignore_polyphonic_notes is true, the highest pitch is used in the melody when multiple notes start at the same time. If false, an exception is raised.

Args

quantized_sequence
A NoteSequence quantized with sequences_lib.quantize_note_sequence.
search_start_step
Start searching for a melody at this time step. Assumed to be the first step of a bar.
instrument
Search for a melody in this instrument number.
gap_bars
If this many bars or more follow a NOTE_OFF event, the melody is ended.
ignore_polyphonic_notes
If True, the highest pitch is used in the melody when multiple notes start at the same time. If False, PolyphonicMelodyError will be raised if multiple notes start at the same time.
pad_end
If True, the end of the melody will be padded with NO_EVENTs so that it will end at a bar boundary.
filter_drums
If True, notes for which is_drum is True will be ignored.

Raises

NonIntegerStepsPerBarError
If quantized_sequence's bar length (derived from its time signature) is not an integer number of time steps.
PolyphonicMelodyError
If any of the notes start on the same step and ignore_polyphonic_notes is False.
Expand source code
def from_quantized_sequence(self,
                            quantized_sequence,
                            search_start_step=0,
                            instrument=0,
                            gap_bars=1,
                            ignore_polyphonic_notes=False,
                            pad_end=False,
                            filter_drums=True):
  """Populate self with a melody from the given quantized NoteSequence.

  A monophonic melody is extracted from the given `instrument` starting at
  `search_start_step`. `instrument` and `search_start_step` can be used to
  drive extraction of multiple melodies from the same quantized sequence. The
  end step of the extracted melody will be stored in `self._end_step`.

  0 velocity notes are ignored. The melody extraction is ended when there are
  no held notes for a time stretch of `gap_bars` in bars (measures) of music.
  The number of time steps per bar is computed from the time signature in
  `quantized_sequence`.

  `ignore_polyphonic_notes` determines what happens when polyphonic (multiple
  notes start at the same time) data is encountered. If
  `ignore_polyphonic_notes` is true, the highest pitch is used in the melody
  when multiple notes start at the same time. If false, an exception is
  raised.

  Args:
    quantized_sequence: A NoteSequence quantized with
        sequences_lib.quantize_note_sequence.
    search_start_step: Start searching for a melody at this time step. Assumed
        to be the first step of a bar.
    instrument: Search for a melody in this instrument number.
    gap_bars: If this many bars or more follow a NOTE_OFF event, the melody
        is ended.
    ignore_polyphonic_notes: If True, the highest pitch is used in the melody
        when multiple notes start at the same time. If False,
        PolyphonicMelodyError will be raised if multiple notes start at
        the same time.
    pad_end: If True, the end of the melody will be padded with NO_EVENTs so
        that it will end at a bar boundary.
    filter_drums: If True, notes for which `is_drum` is True will be ignored.

  Raises:
    NonIntegerStepsPerBarError: If `quantized_sequence`'s bar length
        (derived from its time signature) is not an integer number of time
        steps.
    PolyphonicMelodyError: If any of the notes start on the same step
        and `ignore_polyphonic_notes` is False.
  """
  sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)
  self._reset()

  steps_per_bar_float = sequences_lib.steps_per_bar_in_quantized_sequence(
      quantized_sequence)
  if steps_per_bar_float % 1 != 0:
    raise events_lib.NonIntegerStepsPerBarError(
        'There are %f timesteps per bar. Time signature: %d/%d' %
        (steps_per_bar_float, quantized_sequence.time_signatures[0].numerator,
         quantized_sequence.time_signatures[0].denominator))
  self._steps_per_bar = steps_per_bar = int(steps_per_bar_float)
  self._steps_per_quarter = (
      quantized_sequence.quantization_info.steps_per_quarter)

  # Sort track by note start times, and secondarily by pitch descending.
  notes = sorted([n for n in quantized_sequence.notes
                  if n.instrument == instrument and
                  n.quantized_start_step >= search_start_step],
                 key=lambda note: (note.quantized_start_step, -note.pitch))

  if not notes:
    return

  # The first step in the melody, beginning at the first step of a bar.
  melody_start_step = (
      notes[0].quantized_start_step -
      (notes[0].quantized_start_step - search_start_step) % steps_per_bar)
  for note in notes:
    if filter_drums and note.is_drum:
      continue

    # Ignore 0 velocity notes.
    if not note.velocity:
      continue

    start_index = note.quantized_start_step - melody_start_step
    end_index = note.quantized_end_step - melody_start_step

    if not self._events:
      # If there are no events, we don't need to check for polyphony.
      self._add_note(note.pitch, start_index, end_index)
      continue

    # If `start_index` comes before or lands on an already added note's start
    # step, we cannot add it. In that case either discard the melody or keep
    # the highest pitch.
    last_on, last_off = self._get_last_on_off_events()
    on_distance = start_index - last_on
    off_distance = start_index - last_off
    if on_distance == 0:
      if ignore_polyphonic_notes:
        # Keep highest note.
        # Notes are sorted by pitch descending, so if a note is already at
        # this position its the highest pitch.
        continue
      else:
        self._reset()
        raise PolyphonicMelodyError()
    elif on_distance < 0:
      raise PolyphonicMelodyError(
          'Unexpected note. Not in ascending order.')

    # If a gap of `gap` or more steps is found, end the melody.
    if len(self) and off_distance >= gap_bars * steps_per_bar:  # pylint:disable=len-as-condition
      break

    # Add the note-on and off events to the melody.
    self._add_note(note.pitch, start_index, end_index)

  if not self._events:
    # If no notes were added, don't set `_start_step` and `_end_step`.
    return

  self._start_step = melody_start_step

  # Strip final MELODY_NOTE_OFF event.
  if self._events[-1] == MELODY_NOTE_OFF:
    del self._events[-1]

  length = len(self)
  # Optionally round up `_end_step` to a multiple of `steps_per_bar`.
  if pad_end:
    length += -len(self) % steps_per_bar
  self.set_length(length)
def get_major_key(self)

Finds the major key that this melody most likely belongs to.

If multiple keys match equally, the key with the lowest index is returned, where the indexes of the keys are C Major = 0 through B Major = 11.

Returns

An int for the most likely key (C Major = 0 through B Major = 11)

Expand source code
def get_major_key(self):
  """Finds the major key that this melody most likely belongs to.

  If multiple keys match equally, the key with the lowest index is returned,
  where the indexes of the keys are C Major = 0 through B Major = 11.

  Returns:
    An int for the most likely key (C Major = 0 through B Major = 11)
  """
  key_histogram = self.get_major_key_histogram()
  return key_histogram.argmax()
def get_major_key_histogram(self)

Gets a histogram of the how many notes fit into each key.

Returns

A list of 12 ints, one for each Major key (C Major at index 0 through B Major at index 11). Each int is the total number of notes that could fit into that key.

Expand source code
def get_major_key_histogram(self):
  """Gets a histogram of the how many notes fit into each key.

  Returns:
    A list of 12 ints, one for each Major key (C Major at index 0 through
    B Major at index 11). Each int is the total number of notes that could
    fit into that key.
  """
  note_histogram = self.get_note_histogram()
  key_histogram = np.zeros(NOTES_PER_OCTAVE)
  for note, count in enumerate(note_histogram):
    key_histogram[NOTE_KEYS[note]] += count
  return key_histogram
def get_note_histogram(self)

Gets a histogram of the note occurrences in a melody.

Returns

A list of 12 ints, one for each note value (C at index 0 through B at index 11). Each int is the total number of times that note occurred in the melody.

Expand source code
def get_note_histogram(self):
  """Gets a histogram of the note occurrences in a melody.

  Returns:
    A list of 12 ints, one for each note value (C at index 0 through B at
    index 11). Each int is the total number of times that note occurred in
    the melody.
  """
  np_melody = np.array(self._events, dtype=int)
  return np.bincount(np_melody[np_melody >= MIN_MIDI_PITCH] %
                     NOTES_PER_OCTAVE,
                     minlength=NOTES_PER_OCTAVE)
def increase_resolution(self, k)

Increase the resolution of a Melody.

Increases the resolution of a Melody object by a factor of k. This uses MELODY_NO_EVENT to extend each event in the melody to be k steps long.

Args

k
An integer, the factor by which to increase the resolution of the melody.
Expand source code
def increase_resolution(self, k):
  """Increase the resolution of a Melody.

  Increases the resolution of a Melody object by a factor of `k`. This uses
  MELODY_NO_EVENT to extend each event in the melody to be `k` steps long.

  Args:
    k: An integer, the factor by which to increase the resolution of the
        melody.
  """
  super(Melody, self).increase_resolution(
      k, fill_event=MELODY_NO_EVENT)
def set_length(self, steps, from_left=False)

Sets the length of the melody to the specified number of steps.

If the melody is not long enough, ends any sustained notes and adds NO_EVENT steps for padding. If it is too long, it will be truncated to the requested length.

Args

steps
How many steps long the melody 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 melody to the specified number of steps.

  If the melody is not long enough, ends any sustained notes and adds NO_EVENT
  steps for padding. If it is too long, it will be truncated to the requested
  length.

  Args:
    steps: How many steps long the melody should be.
    from_left: Whether to add/remove from the left instead of right.
  """
  old_len = len(self)
  super(Melody, self).set_length(steps, from_left=from_left)
  if steps > old_len and not from_left:
    # When extending the melody on the right, we end any sustained notes.
    for i in reversed(range(old_len)):
      if self._events[i] == MELODY_NOTE_OFF:
        break
      elif self._events[i] != MELODY_NO_EVENT:
        self._events[old_len] = MELODY_NOTE_OFF
        break
def squash(self, min_note, max_note, transpose_to_key=None)

Transpose and octave shift the notes in this Melody.

The key center of this melody is computed with a heuristic, and the notes are transposed to be in the given key. The melody is also octave shifted to be centered in the given range. Additionally, all notes are octave shifted to lie within a given range.

Args

min_note
Minimum pitch (inclusive) that the resulting notes will take on.
max_note
Maximum pitch (exclusive) that the resulting notes will take on.
transpose_to_key
The melody is transposed to be in this key or None if should not be transposed. 0 = C Major.

Returns

How much notes are transposed by.

Expand source code
def squash(self, min_note, max_note, transpose_to_key=None):
  """Transpose and octave shift the notes in this Melody.

  The key center of this melody is computed with a heuristic, and the notes
  are transposed to be in the given key. The melody is also octave shifted
  to be centered in the given range. Additionally, all notes are octave
  shifted to lie within a given range.

  Args:
    min_note: Minimum pitch (inclusive) that the resulting notes will take on.
    max_note: Maximum pitch (exclusive) that the resulting notes will take on.
    transpose_to_key: The melody is transposed to be in this key or None if
       should not be transposed. 0 = C Major.

  Returns:
    How much notes are transposed by.
  """
  if transpose_to_key is None:
    transpose_amount = 0
  else:
    melody_key = self.get_major_key()
    key_diff = transpose_to_key - melody_key
    midi_notes = [note for note in self._events
                  if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH]
    if not midi_notes:
      return 0
    melody_min_note = min(midi_notes)
    melody_max_note = max(midi_notes)
    melody_center = (melody_min_note + melody_max_note) / 2
    target_center = (min_note + max_note - 1) / 2
    center_diff = target_center - (melody_center + key_diff)
    transpose_amount = (
        key_diff +
        NOTES_PER_OCTAVE * int(round(center_diff / float(NOTES_PER_OCTAVE))))
  self.transpose(transpose_amount, min_note, max_note)

  return transpose_amount
def to_sequence(self, velocity=100, instrument=0, program=0, sequence_start_time=0.0, qpm=120.0)

Converts the Melody to NoteSequence proto.

The end of the melody is treated as a NOTE_OFF event for any sustained notes.

Args

velocity
Midi velocity to give each note. Between 1 and 127 (inclusive).
instrument
Midi instrument to give each note.
program
Midi program to give each note.
sequence_start_time
A time in seconds (float) that the first note in the sequence will land on.
qpm
Quarter notes per minute (float).

Returns

A NoteSequence proto encoding the given melody.

Expand source code
def to_sequence(self,
                velocity=100,
                instrument=0,
                program=0,
                sequence_start_time=0.0,
                qpm=120.0):
  """Converts the Melody to NoteSequence proto.

  The end of the melody is treated as a NOTE_OFF event for any sustained
  notes.

  Args:
    velocity: Midi velocity to give each note. Between 1 and 127 (inclusive).
    instrument: Midi instrument to give each note.
    program: Midi program to give each note.
    sequence_start_time: A time in seconds (float) that the first note in the
        sequence will land on.
    qpm: Quarter notes per minute (float).

  Returns:
    A NoteSequence proto encoding the given melody.
  """
  seconds_per_step = 60.0 / qpm / self.steps_per_quarter

  sequence = music_pb2.NoteSequence()
  sequence.tempos.add().qpm = qpm
  sequence.ticks_per_quarter = STANDARD_PPQ

  sequence_start_time += self.start_step * seconds_per_step
  current_sequence_note = None
  for step, note in enumerate(self):
    if MIN_MIDI_PITCH <= note <= MAX_MIDI_PITCH:
      # End any sustained notes.
      if current_sequence_note is not None:
        current_sequence_note.end_time = (
            step * seconds_per_step + sequence_start_time)

      # Add a note.
      current_sequence_note = sequence.notes.add()
      current_sequence_note.start_time = (
          step * seconds_per_step + sequence_start_time)
      current_sequence_note.pitch = note
      current_sequence_note.velocity = velocity
      current_sequence_note.instrument = instrument
      current_sequence_note.program = program

    elif note == MELODY_NOTE_OFF:
      # End any sustained notes.
      if current_sequence_note is not None:
        current_sequence_note.end_time = (
            step * seconds_per_step + sequence_start_time)
        current_sequence_note = None

  # End any sustained notes.
  if current_sequence_note is not None:
    current_sequence_note.end_time = (
        len(self) * seconds_per_step + sequence_start_time)

  if sequence.notes:
    sequence.total_time = sequence.notes[-1].end_time

  return sequence
def transpose(self, transpose_amount, min_note=0, max_note=128)

Transpose notes in this Melody.

All notes are transposed the specified amount. Additionally, all notes are octave shifted to lie within the [min_note, max_note) range.

Args

transpose_amount
The number of half steps to transpose this Melody. Positive values transpose up. Negative values transpose down.
min_note
Minimum pitch (inclusive) that the resulting notes will take on.
max_note
Maximum pitch (exclusive) that the resulting notes will take on.
Expand source code
def transpose(self, transpose_amount, min_note=0, max_note=128):
  """Transpose notes in this Melody.

  All notes are transposed the specified amount. Additionally, all notes
  are octave shifted to lie within the [min_note, max_note) range.

  Args:
    transpose_amount: The number of half steps to transpose this Melody.
        Positive values transpose up. Negative values transpose down.
    min_note: Minimum pitch (inclusive) that the resulting notes will take on.
    max_note: Maximum pitch (exclusive) that the resulting notes will take on.
  """
  for i in range(len(self)):
    # Transpose MIDI pitches. Special events below MIN_MIDI_PITCH are not
    # changed.
    if self._events[i] >= MIN_MIDI_PITCH:
      self._events[i] += transpose_amount
      if self._events[i] < min_note:
        self._events[i] = (
            min_note + (self._events[i] - min_note) % NOTES_PER_OCTAVE)
      elif self._events[i] >= max_note:
        self._events[i] = (max_note - NOTES_PER_OCTAVE +
                           (self._events[i] - max_note) % NOTES_PER_OCTAVE)
class PolyphonicMelodyError (*args, **kwargs)

Common base class for all non-exit exceptions.

Expand source code
class PolyphonicMelodyError(Exception):
  pass

Ancestors

  • builtins.Exception
  • builtins.BaseException