Exemple #1
0
    def testHistogram(self):
        histo = statistics.Histogram('name_123', [1, 2, 10])
        self.assertEqual(histo.counters, {float('-inf'): 0, 1: 0, 2: 0, 10: 0})
        histo.increment(1)
        self.assertEqual(histo.counters, {float('-inf'): 0, 1: 1, 2: 0, 10: 0})
        histo.increment(3, 3)
        self.assertEqual(histo.counters, {float('-inf'): 0, 1: 1, 2: 3, 10: 0})
        histo.increment(20)
        histo.increment(100)
        self.assertEqual(histo.counters, {float('-inf'): 0, 1: 1, 2: 3, 10: 2})
        histo.increment(0)
        histo.increment(-10)
        self.assertEqual(histo.counters, {float('-inf'): 2, 1: 1, 2: 3, 10: 2})

        histo_2 = statistics.Histogram('name_123', [1, 2, 10])
        histo_2.increment(0, 4)
        histo_2.increment(2, 10)
        histo_2.increment(10, 1)
        histo.merge_from(histo_2)
        self.assertEqual(histo.counters, {
            float('-inf'): 6,
            1: 1,
            2: 13,
            10: 3
        })

        histo_3 = statistics.Histogram('name_123', [1, 2, 7])
        with self.assertRaisesRegex(
                statistics.MergeStatisticsError,
                r'Histogram buckets do not match. '
                r'Expected \[-inf, 1, 2, 10\], got \[-inf, 1, 2, 7\]'):
            histo.merge_from(histo_3)

        class ABC(object):
            pass

        with self.assertRaises(statistics.MergeStatisticsError):
            histo.merge_from(ABC())

        self.assertEqual(
            str(histo),
            'name_123:\n  [-inf,1): 6\n  [1,2): 1\n  [2,10): 13\n  [10,inf): 3'
        )

        histo_copy = histo.copy()
        self.assertEqual(histo_copy.counters, {
            float('-inf'): 6,
            1: 1,
            2: 13,
            10: 3
        })
        self.assertEqual(histo_copy.name, 'name_123')
Exemple #2
0
def extract_polyphonic_sequences(
        quantized_sequence, start_step=0, min_steps_discard=None,
        max_steps_discard=None):
    """Extracts a polyphonic track from the given quantized NoteSequence.

    Currently, this extracts only one polyphonic sequence from a given track.

    Args:
      quantized_sequence: A quantized NoteSequence.
      start_step: Start extracting a sequence at this time step. Assumed
          to be the beginning of a bar.
      min_steps_discard: Minimum length of tracks in steps. Shorter tracks are
          discarded.
      max_steps_discard: Maximum length of tracks in steps. Longer tracks are
          discarded.

    Returns:
      poly_seqs: A python list of PolyphonicSequence instances.
      stats: A dictionary mapping string names to `statistics.Statistic` objects.
    """
    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)

    stats = dict((stat_name, statistics.Counter(stat_name)) for stat_name in
                 ['polyphonic_tracks_discarded_too_short',
                  'polyphonic_tracks_discarded_too_long',
                  'polyphonic_tracks_discarded_more_than_1_program'])

    steps_per_bar = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)

    # Create a histogram measuring lengths (in bars not steps).
    stats['polyphonic_track_lengths_in_bars'] = statistics.Histogram(
        'polyphonic_track_lengths_in_bars',
        [0, 1, 10, 20, 30, 40, 50, 100, 200, 500, 1000])

    # Allow only 1 program.
    programs = set()
    for note in quantized_sequence.notes:
        programs.add(note.program)
    if len(programs) > 1:
        stats['polyphonic_tracks_discarded_more_than_1_program'].increment()
        return [], stats.values()

    # Translate the quantized sequence into a PolyphonicSequence.
    poly_seq = PolyphonicSequence(quantized_sequence,
                                  start_step=start_step)

    poly_seqs = []
    num_steps = poly_seq.num_steps

    if min_steps_discard is not None and num_steps < min_steps_discard:
        stats['polyphonic_tracks_discarded_too_short'].increment()
    elif max_steps_discard is not None and num_steps > max_steps_discard:
        stats['polyphonic_tracks_discarded_too_long'].increment()
    else:
        poly_seqs.append(poly_seq)
        stats['polyphonic_track_lengths_in_bars'].increment(
            num_steps // steps_per_bar)

    return poly_seqs, stats.values()
def extract_polyphonic_sequences(quantized_sequence,
                                 start_step=0,
                                 min_steps_discard=None,
                                 max_steps_discard=None,
                                 mod_writer=None):

    mw = mod_writer

    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)

    stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in [
        'polyphonic_tracks_discarded_too_short',
        'polyphonic_tracks_discarded_too_long',
        'polyphonic_tracks_discarded_more_than_1_program'
    ]])

    steps_per_bar = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)

    # Create a histogram measuring lengths (in bars not steps).
    stats['polyphonic_track_lengths_in_bars'] = statistics.Histogram(
        'polyphonic_track_lengths_in_bars',
        [0, 1, 10, 20, 30, 40, 50, 100, 200, 500, 1000])

    # Allow only 1 program.
    programs = set()
    for note in quantized_sequence.notes:
        programs.add(note.program)
    if len(programs) > 1:
        stats['polyphonic_tracks_discarded_more_than_1_program'].increment()
        return [], stats.values()

    filename = 'quantized_sequence'
    mw.write(mw.model_dir, filename, quantized_sequence)
    poly_seq = PolyphonicSequence(quantized_sequence,
                                  start_step=start_step,
                                  mod_writer=mw)
    quantized_poly_ns = poly_seq.to_sequence()
    quantized_poly_ns.filename = quantized_sequence.filename
    mw.write(mw.model_dir, 'quantized_poly_ns', quantized_poly_ns)

    poly_seqs = []
    num_steps = poly_seq.num_steps

    if min_steps_discard is not None and num_steps < min_steps_discard:
        stats['polyphonic_tracks_discarded_too_short'].increment()
    elif max_steps_discard is not None and num_steps > max_steps_discard:
        stats['polyphonic_tracks_discarded_too_long'].increment()
    else:
        poly_seqs.append(poly_seq)
        stats['polyphonic_track_lengths_in_bars'].increment(num_steps //
                                                            steps_per_bar)
    # pdb.set_trace()
    return poly_seqs, stats.values()
Exemple #4
0
def extract_melodies(quantized_sequence,
                     search_start_step=0,
                     min_bars=7,
                     max_steps_truncate=None,
                     max_steps_discard=None,
                     gap_bars=1.0,
                     min_unique_pitches=5,
                     ignore_polyphonic_notes=True,
                     pad_end=False,
                     filter_drums=True):
    """Extracts a list of melodies from the given quantized NoteSequence.

  This function will search through `quantized_sequence` for monophonic
  melodies in every track at every time step.

  Once a note-on event in a track is encountered, a melody begins.
  Gaps of silence in each track will be splitting points that divide the
  track into separate melodies. The minimum size of these gaps are given
  in `gap_bars`. The size of a bar (measure) of music in time steps is
  computed from the time signature stored in `quantized_sequence`.

  The melody is then checked for validity. The melody is only used if it is
  at least `min_bars` bars long, and has at least `min_unique_pitches` unique
  notes (preventing melodies that only repeat a few notes, such as those found
  in some accompaniment tracks, from being used).

  After scanning each instrument track in the quantized sequence, a list of all
  extracted Melody objects is returned.

  Args:
    quantized_sequence: A quantized NoteSequence.
    search_start_step: Start searching for a melody at this time step. Assumed
        to be the first step of a bar.
    min_bars: Minimum length of melodies in number of bars. Shorter melodies are
        discarded.
    max_steps_truncate: Maximum number of steps in extracted melodies. If
        defined, longer melodies are truncated to this threshold. If pad_end is
        also True, melodies will be truncated to the end of the last bar below
        this threshold.
    max_steps_discard: Maximum number of steps in extracted melodies. If
        defined, longer melodies are discarded.
    gap_bars: A melody comes to an end when this number of bars (measures) of
        silence is encountered.
    min_unique_pitches: Minimum number of unique notes with octave equivalence.
        Melodies with too few unique notes are discarded.
    ignore_polyphonic_notes: If True, melodies will be extracted from
        `quantized_sequence` tracks that contain polyphony (notes start at
        the same time). If False, tracks with polyphony will be ignored.
    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.

  Returns:
    melodies: A python list of Melody instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.

  Raises:
    NonIntegerStepsPerBarException: If `quantized_sequence`'s bar length
        (derived from its time signature) is not an integer number of time
        steps.
  """
    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)

    # TODO(danabo): Convert `ignore_polyphonic_notes` into a float which controls
    # the degree of polyphony that is acceptable.
    melodies = []
    stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in [
        'polyphonic_tracks_discarded', 'melodies_discarded_too_short',
        'melodies_discarded_too_few_pitches', 'melodies_discarded_too_long',
        'melodies_truncated'
    ]])
    # Create a histogram measuring melody lengths (in bars not steps).
    # Capture melodies that are very small, in the range of the filter lower
    # bound `min_bars`, and large. The bucket intervals grow approximately
    # exponentially.
    stats['melody_lengths_in_bars'] = statistics.Histogram(
        'melody_lengths_in_bars', [
            0, 1, 10, 20, 30, 40, 50, 100, 200, 500, min_bars // 2, min_bars,
            min_bars + 1, min_bars - 1
        ])
    instruments = set([n.instrument for n in quantized_sequence.notes])
    steps_per_bar = int(
        sequences_lib.steps_per_bar_in_quantized_sequence(quantized_sequence))
    for instrument in instruments:
        instrument_search_start_step = search_start_step
        # Quantize the track into a Melody object.
        # If any notes start at the same time, only one is kept.
        while 1:
            melody = Melody()
            try:
                melody.from_quantized_sequence(
                    quantized_sequence,
                    instrument=instrument,
                    search_start_step=instrument_search_start_step,
                    gap_bars=gap_bars,
                    ignore_polyphonic_notes=ignore_polyphonic_notes,
                    pad_end=pad_end,
                    filter_drums=filter_drums)
            except PolyphonicMelodyException:
                stats['polyphonic_tracks_discarded'].increment()
                break  # Look for monophonic melodies in other tracks.
            except events_lib.NonIntegerStepsPerBarException:
                raise
            # Start search for next melody on next bar boundary (inclusive).
            instrument_search_start_step = (
                melody.end_step +
                (search_start_step - melody.end_step) % steps_per_bar)
            if not melody:
                break

            # Require a certain melody length.
            if len(melody) < melody.steps_per_bar * min_bars:
                stats['melodies_discarded_too_short'].increment()
                continue

            # Discard melodies that are too long.
            if max_steps_discard is not None and len(
                    melody) > max_steps_discard:
                stats['melodies_discarded_too_long'].increment()
                continue

            # Truncate melodies that are too long.
            if max_steps_truncate is not None and len(
                    melody) > max_steps_truncate:
                truncated_length = max_steps_truncate
                if pad_end:
                    truncated_length -= max_steps_truncate % melody.steps_per_bar
                melody.set_length(truncated_length)
                stats['melodies_truncated'].increment()

            # Require a certain number of unique pitches.
            note_histogram = melody.get_note_histogram()
            unique_pitches = np.count_nonzero(note_histogram)
            if unique_pitches < min_unique_pitches:
                stats['melodies_discarded_too_few_pitches'].increment()
                continue

            # TODO(danabo)
            # Add filter for rhythmic diversity.

            stats['melody_lengths_in_bars'].increment(
                len(melody) // melody.steps_per_bar)

            melodies.append(melody)

    return melodies, list(stats.values())
Exemple #5
0
def extract_pianoroll_sequences(quantized_sequence,
                                start_step=0,
                                min_steps_discard=None,
                                max_steps_discard=None,
                                max_steps_truncate=None):
    """Extracts a polyphonic track from the given quantized NoteSequence.

  Currently, this extracts only one pianoroll from a given track.

  Args:
    quantized_sequence: A quantized NoteSequence.
    start_step: Start extracting a sequence at this time step. Assumed
        to be the beginning of a bar.
    min_steps_discard: Minimum length of tracks in steps. Shorter tracks are
        discarded.
    max_steps_discard: Maximum length of tracks in steps. Longer tracks are
        discarded. Mutually exclusive with `max_steps_truncate`.
    max_steps_truncate: Maximum length of tracks in steps. Longer tracks are
        truncated. Mutually exclusive with `max_steps_discard`.

  Returns:
    pianoroll_seqs: A python list of PianorollSequence instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.

  Raises:
    ValueError: If both `max_steps_discard` and `max_steps_truncate` are
        specified.
  """

    if (max_steps_discard, max_steps_truncate).count(None) == 0:
        raise ValueError(
            'Only one of `max_steps_discard` and `max_steps_truncate` can be '
            'specified.')
    sequences_lib.assert_is_relative_quantized_sequence(quantized_sequence)

    # pylint: disable=g-complex-comprehension
    stats = dict((stat_name, statistics.Counter(stat_name)) for stat_name in [
        'pianoroll_tracks_truncated_too_long',
        'pianoroll_tracks_discarded_too_short',
        'pianoroll_tracks_discarded_too_long',
        'pianoroll_tracks_discarded_more_than_1_program'
    ])
    # pylint: enable=g-complex-comprehension

    steps_per_bar = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)

    # Create a histogram measuring lengths (in bars not steps).
    stats['pianoroll_track_lengths_in_bars'] = statistics.Histogram(
        'pianoroll_track_lengths_in_bars',
        [0, 1, 10, 20, 30, 40, 50, 100, 200, 500, 1000])

    # Allow only 1 program.
    programs = set()
    for note in quantized_sequence.notes:
        programs.add(note.program)
    if len(programs) > 1:
        stats['pianoroll_tracks_discarded_more_than_1_program'].increment()
        return [], list(stats.values())

    # Translate the quantized sequence into a PianorollSequence.
    pianoroll_seq = PianorollSequence(quantized_sequence=quantized_sequence,
                                      start_step=start_step)

    pianoroll_seqs = []
    num_steps = pianoroll_seq.num_steps

    if min_steps_discard is not None and num_steps < min_steps_discard:
        stats['pianoroll_tracks_discarded_too_short'].increment()
    elif max_steps_discard is not None and num_steps > max_steps_discard:
        stats['pianoroll_tracks_discarded_too_long'].increment()
    else:
        if max_steps_truncate is not None and num_steps > max_steps_truncate:
            stats['pianoroll_tracks_truncated_too_long'].increment()
            pianoroll_seq.set_length(max_steps_truncate)
        pianoroll_seqs.append(pianoroll_seq)
        stats['pianoroll_track_lengths_in_bars'].increment(num_steps //
                                                           steps_per_bar)
    return pianoroll_seqs, list(stats.values())
Exemple #6
0
def extract_drum_tracks(quantized_sequence,
                        search_start_step=0,
                        min_bars=7,
                        max_steps_truncate=None,
                        max_steps_discard=None,
                        gap_bars=1.0,
                        pad_end=False,
                        ignore_is_drum=False):
    """Extracts a list of drum tracks from the given quantized NoteSequence.

  This function will search through `quantized_sequence` for drum tracks. A drum
  track can span multiple "tracks" in the sequence. Only one drum track can be
  active at a given time, but multiple drum tracks can be extracted from the
  sequence if gaps are present.

  Once a note-on drum event is encountered, a drum track begins. Gaps of silence
  will be splitting points that divide the sequence into separate drum tracks.
  The minimum size of these gaps are given in `gap_bars`. The size of a bar
  (measure) of music in time steps is computed form the time signature stored in
  `quantized_sequence`.

  A drum track is only used if it is at least `min_bars` bars long.

  After scanning the quantized NoteSequence, a list of all extracted DrumTrack
  objects is returned.

  Args:
    quantized_sequence: A quantized NoteSequence.
    search_start_step: Start searching for drums at this time step. Assumed to
        be the beginning of a bar.
    min_bars: Minimum length of drum tracks in number of bars. Shorter drum
        tracks are discarded.
    max_steps_truncate: Maximum number of steps in extracted drum tracks. If
        defined, longer drum tracks are truncated to this threshold. If pad_end
        is also True, drum tracks will be truncated to the end of the last bar
        below this threshold.
    max_steps_discard: Maximum number of steps in extracted drum tracks. If
        defined, longer drum tracks are discarded.
    gap_bars: A drum track comes to an end when this number of bars (measures)
        of no drums is encountered.
    pad_end: If True, the end of the drum track will be padded with empty events
        so that it will end at a bar boundary.
    ignore_is_drum: Whether accept notes where `is_drum` is False.

  Returns:
    drum_tracks: A python list of DrumTrack instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.

  Raises:
    NonIntegerStepsPerBarException: If `quantized_sequence`'s bar length
        (derived from its time signature) is not an integer number of time
        steps.
  """
    drum_tracks = []
    stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in [
        'drum_tracks_discarded_too_short', 'drum_tracks_discarded_too_long',
        'drum_tracks_truncated'
    ]])
    # Create a histogram measuring drum track lengths (in bars not steps).
    # Capture drum tracks that are very small, in the range of the filter lower
    # bound `min_bars`, and large. The bucket intervals grow approximately
    # exponentially.
    stats['drum_track_lengths_in_bars'] = statistics.Histogram(
        'drum_track_lengths_in_bars', [
            0, 1, 10, 20, 30, 40, 50, 100, 200, 500, min_bars // 2, min_bars,
            min_bars + 1, min_bars - 1
        ])

    steps_per_bar = int(
        sequences_lib.steps_per_bar_in_quantized_sequence(quantized_sequence))

    # Quantize the track into a DrumTrack object.
    # If any notes start at the same time, only one is kept.
    while 1:
        drum_track = DrumTrack()
        try:
            drum_track.from_quantized_sequence(
                quantized_sequence,
                search_start_step=search_start_step,
                gap_bars=gap_bars,
                pad_end=pad_end,
                ignore_is_drum=ignore_is_drum)
        except events_lib.NonIntegerStepsPerBarException:
            raise
        search_start_step = (
            drum_track.end_step +
            (search_start_step - drum_track.end_step) % steps_per_bar)
        if not drum_track:
            break

        # Require a certain drum track length.
        if len(drum_track) < drum_track.steps_per_bar * min_bars:
            stats['drum_tracks_discarded_too_short'].increment()
            continue

        # Discard drum tracks that are too long.
        if max_steps_discard is not None and len(
                drum_track) > max_steps_discard:
            stats['drum_tracks_discarded_too_long'].increment()
            continue

        # Truncate drum tracks that are too long.
        if max_steps_truncate is not None and len(
                drum_track) > max_steps_truncate:
            truncated_length = max_steps_truncate
            if pad_end:
                truncated_length -= max_steps_truncate % drum_track.steps_per_bar
            drum_track.set_length(truncated_length)
            stats['drum_tracks_truncated'].increment()

        stats['drum_track_lengths_in_bars'].increment(
            len(drum_track) // drum_track.steps_per_bar)

        drum_tracks.append(drum_track)

    return drum_tracks, stats.values()
Exemple #7
0
def extract_performances(
    quantized_sequence, start_step=0, min_events_discard=None,
    max_events_truncate=None, max_steps_truncate=None, num_velocity_bins=0,
    split_instruments=False, note_performance=False):
  """Extracts one or more performances from the given quantized NoteSequence.

  Args:
    quantized_sequence: A quantized NoteSequence.
    start_step: Start extracting a sequence at this time step.
    min_events_discard: Minimum length of tracks in events. Shorter tracks are
        discarded.
    max_events_truncate: Maximum length of tracks in events. Longer tracks are
        truncated.
    max_steps_truncate: Maximum length of tracks in quantized time steps. Longer
        tracks are truncated.
    num_velocity_bins: Number of velocity bins to use. If 0, velocity events
        will not be included at all.
    split_instruments: If True, will extract a performance for each instrument.
        Otherwise, will extract a single performance.
    note_performance: If True, will create a NotePerformance object. If
        False, will create either a MetricPerformance or Performance based on
        how the sequence was quantized.

  Returns:
    performances: A python list of Performance or MetricPerformance (if
        `quantized_sequence` is quantized relative to meter) instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.
  """
  sequences_lib.assert_is_quantized_sequence(quantized_sequence)

  stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in
                ['performances_discarded_too_short',
                 'performances_truncated', 'performances_truncated_timewise',
                 'performances_discarded_more_than_1_program',
                 'performance_discarded_too_many_time_shift_steps',
                 'performance_discarded_too_many_duration_steps']])

  if sequences_lib.is_absolute_quantized_sequence(quantized_sequence):
    steps_per_second = quantized_sequence.quantization_info.steps_per_second
    # Create a histogram measuring lengths in seconds.
    stats['performance_lengths_in_seconds'] = statistics.Histogram(
        'performance_lengths_in_seconds',
        [5, 10, 20, 30, 40, 60, 120])
  else:
    steps_per_bar = sequences_lib.steps_per_bar_in_quantized_sequence(
        quantized_sequence)
    # Create a histogram measuring lengths in bars.
    stats['performance_lengths_in_bars'] = statistics.Histogram(
        'performance_lengths_in_bars',
        [1, 10, 20, 30, 40, 50, 100, 200, 500])

  if split_instruments:
    instruments = set(note.instrument for note in quantized_sequence.notes)
  else:
    instruments = set([None])
    # Allow only 1 program.
    programs = set()
    for note in quantized_sequence.notes:
      programs.add(note.program)
    if len(programs) > 1:
      stats['performances_discarded_more_than_1_program'].increment()
      return [], stats.values()

  performances = []

  for instrument in instruments:
    # Translate the quantized sequence into a Performance.
    if note_performance:
      try:
        performance = NotePerformance(
            quantized_sequence, start_step=start_step,
            num_velocity_bins=num_velocity_bins, instrument=instrument)
      except NotePerformanceTooManyTimeShiftSteps:
        stats['performance_discarded_too_many_time_shift_steps'].increment()
        continue
      except NotePerformanceTooManyDurationSteps:
        stats['performance_discarded_too_many_duration_steps'].increment()
        continue
    elif sequences_lib.is_absolute_quantized_sequence(quantized_sequence):
      performance = Performance(quantized_sequence, start_step=start_step,
                                num_velocity_bins=num_velocity_bins,
                                instrument=instrument)
    else:
      performance = MetricPerformance(quantized_sequence, start_step=start_step,
                                      num_velocity_bins=num_velocity_bins,
                                      instrument=instrument)

    if (max_steps_truncate is not None and
        performance.num_steps > max_steps_truncate):
      performance.set_length(max_steps_truncate)
      stats['performances_truncated_timewise'].increment()

    if (max_events_truncate is not None and
        len(performance) > max_events_truncate):
      performance.truncate(max_events_truncate)
      stats['performances_truncated'].increment()

    if min_events_discard is not None and len(performance) < min_events_discard:
      stats['performances_discarded_too_short'].increment()
    else:
      performances.append(performance)
      if sequences_lib.is_absolute_quantized_sequence(quantized_sequence):
        stats['performance_lengths_in_seconds'].increment(
            performance.num_steps // steps_per_second)
      else:
        stats['performance_lengths_in_bars'].increment(
            performance.num_steps // steps_per_bar)

  return performances, stats.values()
def extract_melodies(quantized_sequence,
                     min_bars=7,
                     gap_bars=1.0,
                     min_unique_pitches=5,
                     ignore_polyphonic_notes=True):
  """Extracts a list of melodies from the given QuantizedSequence object.

  This function will search through `quantized_sequence` for monophonic
  melodies in every track at every time step.

  Once a note-on event in a track is encountered, a melody begins.
  Gaps of silence in each track will be splitting points that divide the
  track into separate melodies. The minimum size of these gaps are given
  in `gap_bars`. The size of a bar (measure) of music in time steps is
  computed from the time signature stored in `quantized_sequence`.

  The melody is then checked for validity. The melody is only used if it is
  at least `min_bars` bars long, and has at least `min_unique_pitches` unique
  notes (preventing melodies that only repeat a few notes, such as those found
  in some accompaniment tracks, from being used).

  After scanning each instrument track in the NoteSequence, a list of all the
  valid melodies is returned.

  Args:
    quantized_sequence: A sequences_lib.QuantizedSequence object.
    min_bars: Minimum length of melodies in number of bars. Shorter melodies are
        discarded.
    gap_bars: A melody comes to an end when this number of bars (measures) of
        silence is encountered.
    min_unique_pitches: Minimum number of unique notes with octave equivalence.
        Melodies with too few unique notes are discarded.
    ignore_polyphonic_notes: If True, melodies will be extracted from
      `quantized_sequence` tracks that contain polyphony (notes start at
      the same time). If False, tracks with polyphony will be ignored.

  Returns:
    melodies: A python list of MonophonicMelody instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.
  """
  # TODO(danabo): Convert `ignore_polyphonic_notes` into a float which controls
  # the degree of polyphony that is acceptable.
  melodies = []
  stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in
                ['polyphonic_tracks_discarded',
                 'melodies_discarded_too_short',
                 'melodies_discarded_too_few_pitches']])
  # Create a histogram measuring melody lengths (in bars not steps).
  # Capture melodies that are very small, in the range of the filter lower
  # bound `min_bars`, and large. The bucket intervals grow approximately
  # exponentially.
  stats['melody_lengths_in_bars'] = statistics.Histogram(
      'melody_lengths_in_bars',
      [0, 1, 10, 20, 30, 40, 50, 100, 200, 500, min_bars // 2, min_bars,
       min_bars + 1, min_bars - 1])
  for track in quantized_sequence.tracks:
    start = 0

    # Quantize the track into a MonophonicMelody object.
    # If any notes start at the same time, only one is kept.
    while 1:
      melody = MonophonicMelody()
      try:
        melody.from_quantized_sequence(
            quantized_sequence,
            track=track,
            start_step=start,
            gap_bars=gap_bars,
            ignore_polyphonic_notes=ignore_polyphonic_notes)
      except PolyphonicMelodyException:
        stats['polyphonic_tracks_discarded'].increment()
        break  # Look for monophonic melodies in other tracks.
      start = melody.end_step
      if not melody:
        break

      # Require a certain melody length.
      stats['melody_lengths_in_bars'].increment(
          len(melody) // melody.steps_per_bar)
      if len(melody) - 1 < melody.steps_per_bar * min_bars:
        stats['melodies_discarded_too_short'].increment()
        continue

      # Require a certain number of unique pitches.
      note_histogram = melody.get_note_histogram()
      unique_pitches = np.count_nonzero(note_histogram)
      if unique_pitches < min_unique_pitches:
        stats['melodies_discarded_too_few_pitches'].increment()
        continue

      # TODO(danabo)
      # Add filter for rhythmic diversity.

      melodies.append(melody)

  return melodies, stats.values()
def extract_performances(quantized_sequence,
                         start_step=0,
                         min_events_discard=None,
                         max_events_truncate=None,
                         num_velocity_bins=0):
    """Extracts a performance from the given quantized NoteSequence.

  Currently, this extracts only one performance from a given track.

  Args:
    quantized_sequence: A quantized NoteSequence.
    start_step: Start extracting a sequence at this time step.
    min_events_discard: Minimum length of tracks in events. Shorter tracks are
        discarded.
    max_events_truncate: Maximum length of tracks in events. Longer tracks are
        truncated.
    num_velocity_bins: Number of velocity bins to use. If 0, velocity events
        will not be included at all.

  Returns:
    performances: A python list of Performance instances.
    stats: A dictionary mapping string names to `statistics.Statistic` objects.
  """
    sequences_lib.assert_is_absolute_quantized_sequence(quantized_sequence)

    stats = dict([(stat_name, statistics.Counter(stat_name)) for stat_name in [
        'performances_discarded_too_short', 'performances_truncated',
        'performances_discarded_more_than_1_program'
    ]])

    steps_per_second = quantized_sequence.quantization_info.steps_per_second

    # Create a histogram measuring lengths (in bars not steps).
    stats['performance_lengths_in_seconds'] = statistics.Histogram(
        'performance_lengths_in_seconds', [5, 10, 20, 30, 40, 60, 120])

    # Allow only 1 program.
    programs = set()
    for note in quantized_sequence.notes:
        programs.add(note.program)
    if len(programs) > 1:
        stats['performances_discarded_more_than_1_program'].increment()
        return [], stats.values()

    performances = []

    # Translate the quantized sequence into a Performance.
    performance = Performance(quantized_sequence,
                              start_step=start_step,
                              num_velocity_bins=num_velocity_bins)

    if (max_events_truncate is not None
            and len(performance) > max_events_truncate):
        performance.truncate(max_events_truncate)
        stats['performances_truncated'].increment()

    if min_events_discard is not None and len(
            performance) < min_events_discard:
        stats['performances_discarded_too_short'].increment()
    else:
        performances.append(performance)
        stats['performance_lengths_in_seconds'].increment(
            performance.num_steps // steps_per_second)

    return performances, stats.values()