Beispiel #1
0
def split_on_downbeats(sequence,
                       bars_per_segment,
                       downbeats=None,
                       skip_bars=0,
                       min_notes_per_segment=0,
                       include_span=False,
                       tolerance=1e-6):
    """Split a note sequence on bar boundaries.

    Args:
        sequence: A `NoteSequence`.
        bars_per_segment: The number of bars (downbeats) per segment.
        downbeats: Downbeat times in seconds. If not specified, they will be determined using
            `pretty_midi`.
        skip_bars: The index of the bar to start at.
        min_notes_per_segment: The minimum required number of notes per segment. Segments
            containing fewer notes will be skipped.
        include_span: If `True`, the output will be tuples of the form
            `(start_bar_idx, end_bar_idx, segment)`.
        tolerance: The maximum time in seconds a note can precede a downbeat to be included in the
            following bar.
    Yields:
        `NoteSequence`s, or tuples of the form `(start_bar_idx, end_bar_idx, segment)`.
    """
    if downbeats is None:
        downbeats = midi_io.sequence_proto_to_pretty_midi(
            sequence).get_downbeats()
    downbeats = [d - tolerance for d in downbeats]
    downbeats = [d for d in downbeats if d < sequence.total_time]

    try:
        iter(bars_per_segment)
    except TypeError:
        bars_per_segment = [bars_per_segment]

    for bps in bars_per_segment:
        first_split = skip_bars or bps  # Do not split at time 0
        split_times = list(downbeats[first_split::bps])
        segments = sequences_lib.split_note_sequence(
            sequence, hop_size_seconds=split_times)
        if skip_bars:
            # The first segment will contain the bars we want to skip
            segments.pop(0)

        for i, segment in enumerate(segments):
            start = skip_bars + i * bps
            end = start + bps

            if len(segment.notes) < min_notes_per_segment:
                print(
                    f'Skipping segment {start}-{end} with {len(segment.notes)} notes',
                    file=sys.stderr)
                continue

            if include_span:
                yield start, end, segment
            else:
                yield segment
Beispiel #2
0
  def testSplitNoteSequenceSkipSplitsInsideNotes(self):
    # Tests splitting a NoteSequence at regular hop size, skipping splits that
    # would have occurred inside a note.
    sequence = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        sequence, 0,
        [(12, 100, 0.01, 3.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50),
         (55, 120, 4.0, 4.01), (52, 99, 4.75, 5.0)])
    testing_lib.add_chords_to_sequence(
        sequence, [('C', 0.0), ('G7', 3.0), ('F', 4.5)])

    expected_subsequence_1 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_1, 0,
        [(12, 100, 0.01, 3.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_1, [('C', 0.0), ('G7', 3.0)])
    expected_subsequence_1.total_time = 3.50
    expected_subsequence_1.subsequence_info.end_time_offset = 1.5

    expected_subsequence_2 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_2, 0,
        [(55, 120, 0.0, 0.01), (52, 99, 0.75, 1.0)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_2, [('G7', 0.0), ('F', 0.5)])
    expected_subsequence_2.total_time = 1.0
    expected_subsequence_2.subsequence_info.start_time_offset = 4.0

    subsequences = sequences_lib.split_note_sequence(
        sequence, hop_size_seconds=2.0, skip_splits_inside_notes=True)
    self.assertEquals(2, len(subsequences))
    self.assertProtoEquals(expected_subsequence_1, subsequences[0])
    self.assertProtoEquals(expected_subsequence_2, subsequences[1])
Beispiel #3
0
  def testSplitNoteSequenceSkipSplitsInsideNotes(self):
    # Tests splitting a NoteSequence at regular hop size, skipping splits that
    # would have occurred inside a note.
    sequence = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        sequence, 0,
        [(12, 100, 0.01, 3.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50),
         (55, 120, 4.0, 4.01), (52, 99, 4.75, 5.0)])
    testing_lib.add_chords_to_sequence(
        sequence, [('C', 0.0), ('G7', 3.0), ('F', 4.5)])

    expected_subsequence_1 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_1, 0,
        [(12, 100, 0.01, 3.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_1, [('C', 0.0), ('G7', 3.0)])
    expected_subsequence_1.total_time = 3.50
    expected_subsequence_1.subsequence_info.end_time_offset = 1.5

    expected_subsequence_2 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_2, 0,
        [(55, 120, 0.0, 0.01), (52, 99, 0.75, 1.0)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_2, [('G7', 0.0), ('F', 0.5)])
    expected_subsequence_2.total_time = 1.0
    expected_subsequence_2.subsequence_info.start_time_offset = 4.0

    subsequences = sequences_lib.split_note_sequence(
        sequence, hop_size_seconds=2.0, skip_splits_inside_notes=True)
    self.assertEquals(2, len(subsequences))
    self.assertProtoEquals(expected_subsequence_1, subsequences[0])
    self.assertProtoEquals(expected_subsequence_2, subsequences[1])
  def testSplitter(self):
    note_sequence = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        note_sequence, 0,
        [(12, 100, 0.01, 10.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50),
         (55, 120, 4.0, 4.01), (52, 99, 4.75, 5.0)])
    expected_sequences = sequences_lib.split_note_sequence(note_sequence, 1.0)

    unit = note_sequence_pipelines.Splitter(1.0)
    self._unit_transform_test(unit, note_sequence, expected_sequences)
  def testSplitter(self):
    note_sequence = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        note_sequence, 0,
        [(12, 100, 0.01, 10.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50),
         (55, 120, 4.0, 4.01), (52, 99, 4.75, 5.0)])
    expected_sequences = sequences_lib.split_note_sequence(note_sequence, 1.0)

    unit = note_sequence_pipelines.Splitter(1.0)
    self._unit_transform_test(unit, note_sequence, expected_sequences)
Beispiel #6
0
    def _to_tensors(self, note_sequence):
        # Performance sequences require sustain to be correctly interpreted.
        note_sequence = sequences_lib.apply_sustain_control_changes(
            note_sequence)

        if self._chord_encoding and not any(
                ta.annotation_type == CHORD_SYMBOL
                for ta in note_sequence.text_annotations):
            try:
                # Quantize just for the purpose of chord inference.
                # TODO(iansimon): Allow chord inference in unquantized sequences.
                quantized_sequence = mm.quantize_note_sequence(
                    note_sequence, self._steps_per_quarter)
                if (mm.steps_per_bar_in_quantized_sequence(quantized_sequence)
                        != self._steps_per_bar):
                    return data.ConverterTensors()

                # Infer chords in quantized sequence.
                mm.infer_chords_for_sequence(quantized_sequence)

            except (mm.BadTimeSignatureException,
                    mm.NonIntegerStepsPerBarException,
                    mm.NegativeTimeException, mm.ChordInferenceException):
                return data.ConverterTensors()

            # Copy inferred chords back to original sequence.
            for qta in quantized_sequence.text_annotations:
                if qta.annotation_type == CHORD_SYMBOL:
                    ta = note_sequence.text_annotations.add()
                    ta.annotation_type = CHORD_SYMBOL
                    ta.time = qta.time
                    ta.text = qta.text

        quarters_per_minute = (note_sequence.tempos[0].qpm
                               if note_sequence.tempos else
                               mm.DEFAULT_QUARTERS_PER_MINUTE)
        quarters_per_bar = self._steps_per_bar / self._steps_per_quarter
        hop_size_quarters = quarters_per_bar * self._hop_size_bars
        hop_size_seconds = 60.0 * hop_size_quarters / quarters_per_minute

        # Split note sequence by bar hop size (in seconds).
        subsequences = sequences_lib.split_note_sequence(
            note_sequence, hop_size_seconds)

        if self._first_subsequence_only and len(subsequences) > 1:
            return data.ConverterTensors()

        sequence_tensors = []
        sequence_chord_tensors = []

        for subsequence in subsequences:
            # Quantize this subsequence.
            try:
                quantized_subsequence = mm.quantize_note_sequence(
                    subsequence, self._steps_per_quarter)
                if (mm.steps_per_bar_in_quantized_sequence(
                        quantized_subsequence) != self._steps_per_bar):
                    return data.ConverterTensors()
            except (mm.BadTimeSignatureException,
                    mm.NonIntegerStepsPerBarException,
                    mm.NegativeTimeException):
                return data.ConverterTensors()

            # Convert the quantized subsequence to tensors.
            tensors, chord_tensors = self._quantized_subsequence_to_tensors(
                quantized_subsequence)
            if tensors:
                sequence_tensors.append(tensors)
                if self._chord_encoding:
                    sequence_chord_tensors.append(chord_tensors)

        return data.ConverterTensors(inputs=sequence_tensors,
                                     outputs=sequence_tensors,
                                     controls=sequence_chord_tensors)
 def transform(self, note_sequence):
   return sequences_lib.split_note_sequence(
       note_sequence, self._hop_size_seconds)
  def process(self, kv):
    # Seed random number generator based on key so that hop times are
    # deterministic.
    key, ns_str = kv
    m = hashlib.md5(key)
    random.seed(int(m.hexdigest(), 16))

    # Deserialize NoteSequence proto.
    ns = music_pb2.NoteSequence.FromString(ns_str)

    # Apply sustain pedal.
    ns = sequences_lib.apply_sustain_control_changes(ns)

    # Remove control changes as there are potentially a lot of them and they are
    # no longer needed.
    del ns.control_changes[:]

    if (self._min_hop_size_seconds and
        ns.total_time < self._min_hop_size_seconds):
      Metrics.counter('extract_examples', 'sequence_too_short').inc()
      return

    sequences = []
    for _ in range(self._num_replications):
      if self._max_hop_size_seconds:
        if self._max_hop_size_seconds == self._min_hop_size_seconds:
          # Split using fixed hop size.
          sequences += sequences_lib.split_note_sequence(
              ns, self._max_hop_size_seconds)
        else:
          # Sample random hop positions such that each segment size is within
          # the specified range.
          hop_times = [0.0]
          while hop_times[-1] <= ns.total_time - self._min_hop_size_seconds:
            if hop_times[-1] + self._max_hop_size_seconds < ns.total_time:
              # It's important that we get a valid hop size here, since the
              # remainder of the sequence is too long.
              max_offset = min(
                  self._max_hop_size_seconds,
                  ns.total_time - self._min_hop_size_seconds - hop_times[-1])
            else:
              # It's okay if the next hop time is invalid (in which case we'll
              # just stop).
              max_offset = self._max_hop_size_seconds
            offset = random.uniform(self._min_hop_size_seconds, max_offset)
            hop_times.append(hop_times[-1] + offset)
          # Split at the chosen hop times (ignoring zero and the final invalid
          # time).
          sequences += sequences_lib.split_note_sequence(ns, hop_times[1:-1])
      else:
        sequences += [ns]

    for performance_sequence in sequences:
      if self._encode_score_fns:
        # We need to extract a score.
        if not self._absolute_timing:
          # Beats are required to extract a score with metric timing.
          beats = [
              ta for ta in performance_sequence.text_annotations
              if (ta.annotation_type ==
                  music_pb2.NoteSequence.TextAnnotation.BEAT)
              and ta.time <= performance_sequence.total_time
          ]
          if len(beats) < 2:
            Metrics.counter('extract_examples', 'not_enough_beats').inc()
            continue

          # Ensure the sequence starts and ends on a beat.
          performance_sequence = sequences_lib.extract_subsequence(
              performance_sequence,
              start_time=min(beat.time for beat in beats),
              end_time=max(beat.time for beat in beats)
          )

          # Infer beat-aligned chords (only for relative timing).
          try:
            chord_inference.infer_chords_for_sequence(
                performance_sequence,
                chord_change_prob=0.25,
                chord_note_concentration=50.0,
                add_key_signatures=True)
          except chord_inference.ChordInferenceError:
            Metrics.counter('extract_examples', 'chord_inference_failed').inc()
            continue

        # Infer melody regardless of relative/absolute timing.
        try:
          melody_instrument = melody_inference.infer_melody_for_sequence(
              performance_sequence,
              melody_interval_scale=2.0,
              rest_prob=0.1,
              instantaneous_non_max_pitch_prob=1e-15,
              instantaneous_non_empty_rest_prob=0.0,
              instantaneous_missing_pitch_prob=1e-15)
        except melody_inference.MelodyInferenceError:
          Metrics.counter('extract_examples', 'melody_inference_failed').inc()
          continue

        if not self._absolute_timing:
          # Now rectify detected beats to occur at fixed tempo.
          # TODO(iansimon): also include the alignment
          score_sequence, unused_alignment = sequences_lib.rectify_beats(
              performance_sequence, beats_per_minute=SCORE_BPM)
        else:
          # Score uses same timing as performance.
          score_sequence = copy.deepcopy(performance_sequence)

        # Remove melody notes from performance.
        performance_notes = []
        for note in performance_sequence.notes:
          if note.instrument != melody_instrument:
            performance_notes.append(note)
        del performance_sequence.notes[:]
        performance_sequence.notes.extend(performance_notes)

        # Remove non-melody notes from score.
        score_notes = []
        for note in score_sequence.notes:
          if note.instrument == melody_instrument:
            score_notes.append(note)
        del score_sequence.notes[:]
        score_sequence.notes.extend(score_notes)

        # Remove key signatures and beat/chord annotations from performance.
        del performance_sequence.key_signatures[:]
        del performance_sequence.text_annotations[:]

        Metrics.counter('extract_examples', 'extracted_score').inc()

      for augment_fn in self._augment_fns:
        # Augment and encode the performance.
        try:
          augmented_performance_sequence = augment_fn(performance_sequence)
        except DataAugmentationError:
          Metrics.counter(
              'extract_examples', 'augment_performance_failed').inc()
          continue
        example_dict = {
            'targets': self._encode_performance_fn(
                augmented_performance_sequence)
        }
        if not example_dict['targets']:
          Metrics.counter('extract_examples', 'skipped_empty_targets').inc()
          continue

        if self._encode_score_fns:
          # Augment the extracted score.
          try:
            augmented_score_sequence = augment_fn(score_sequence)
          except DataAugmentationError:
            Metrics.counter('extract_examples', 'augment_score_failed').inc()
            continue

          # Apply all score encoding functions.
          skip = False
          for name, encode_score_fn in self._encode_score_fns.items():
            example_dict[name] = encode_score_fn(augmented_score_sequence)
            if not example_dict[name]:
              Metrics.counter('extract_examples',
                              'skipped_empty_%s' % name).inc()
              skip = True
              break
          if skip:
            continue

        Metrics.counter('extract_examples', 'encoded_example').inc()
        Metrics.distribution(
            'extract_examples', 'performance_length_in_seconds').update(
                int(augmented_performance_sequence.total_time))

        yield generator_utils.to_example(example_dict)
 def transform(self, note_sequence):
     return sequences_lib.split_note_sequence(note_sequence,
                                              self._hop_size_seconds)
Beispiel #10
0
    def testSplitNoteSequence(self):
        # Tests splitting a NoteSequence at regular hop size, truncating notes.
        sequence = common_testing_lib.parse_test_proto(
            music_pb2.NoteSequence, """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
        testing_lib.add_track_to_sequence(sequence, 0, [(12, 100, 0.01, 8.0),
                                                        (11, 55, 0.22, 0.50),
                                                        (40, 45, 2.50, 3.50),
                                                        (55, 120, 4.0, 4.01),
                                                        (52, 99, 4.75, 5.0)])
        testing_lib.add_chords_to_sequence(sequence, [('C', 1.0), ('G7', 2.0),
                                                      ('F', 4.0)])

        expected_subsequence_1 = common_testing_lib.parse_test_proto(
            music_pb2.NoteSequence, """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
        testing_lib.add_track_to_sequence(expected_subsequence_1, 0,
                                          [(12, 100, 0.01, 3.0),
                                           (11, 55, 0.22, 0.50),
                                           (40, 45, 2.50, 3.0)])
        testing_lib.add_chords_to_sequence(expected_subsequence_1,
                                           [('C', 1.0), ('G7', 2.0)])
        expected_subsequence_1.total_time = 3.0
        expected_subsequence_1.subsequence_info.end_time_offset = 5.0

        expected_subsequence_2 = common_testing_lib.parse_test_proto(
            music_pb2.NoteSequence, """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
        testing_lib.add_track_to_sequence(expected_subsequence_2, 0,
                                          [(55, 120, 1.0, 1.01),
                                           (52, 99, 1.75, 2.0)])
        testing_lib.add_chords_to_sequence(expected_subsequence_2,
                                           [('G7', 0.0), ('F', 1.0)])
        expected_subsequence_2.total_time = 2.0
        expected_subsequence_2.subsequence_info.start_time_offset = 3.0
        expected_subsequence_2.subsequence_info.end_time_offset = 3.0

        expected_subsequence_3 = common_testing_lib.parse_test_proto(
            music_pb2.NoteSequence, """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
        testing_lib.add_chords_to_sequence(expected_subsequence_3,
                                           [('F', 0.0)])
        expected_subsequence_3.total_time = 0.0
        expected_subsequence_3.subsequence_info.start_time_offset = 6.0
        expected_subsequence_3.subsequence_info.end_time_offset = 2.0

        subsequences = sequences_lib.split_note_sequence(sequence,
                                                         hop_size_seconds=3.0)
        self.assertEquals(3, len(subsequences))
        self.assertProtoEquals(expected_subsequence_1, subsequences[0])
        self.assertProtoEquals(expected_subsequence_2, subsequences[1])
        self.assertProtoEquals(expected_subsequence_3, subsequences[2])
  def _to_tensors(self, note_sequence):
    # Performance sequences require sustain to be correctly interpreted.
    note_sequence = sequences_lib.apply_sustain_control_changes(note_sequence)

    if self._chord_encoding and not any(
        ta.annotation_type == CHORD_SYMBOL
        for ta in note_sequence.text_annotations):
      try:
        # Quantize just for the purpose of chord inference.
        # TODO(iansimon): Allow chord inference in unquantized sequences.
        quantized_sequence = mm.quantize_note_sequence(
            note_sequence, self._steps_per_quarter)
        if (mm.steps_per_bar_in_quantized_sequence(quantized_sequence) !=
            self._steps_per_bar):
          return data.ConverterTensors()

        # Infer chords in quantized sequence.
        mm.infer_chords_for_sequence(quantized_sequence)

      except (mm.BadTimeSignatureError, mm.NonIntegerStepsPerBarError,
              mm.NegativeTimeError, mm.ChordInferenceError):
        return data.ConverterTensors()

      # Copy inferred chords back to original sequence.
      for qta in quantized_sequence.text_annotations:
        if qta.annotation_type == CHORD_SYMBOL:
          ta = note_sequence.text_annotations.add()
          ta.annotation_type = CHORD_SYMBOL
          ta.time = qta.time
          ta.text = qta.text

    if note_sequence.tempos:
      quarters_per_minute = note_sequence.tempos[0].qpm
    else:
      quarters_per_minute = mm.DEFAULT_QUARTERS_PER_MINUTE
    quarters_per_bar = self._steps_per_bar / self._steps_per_quarter
    hop_size_quarters = quarters_per_bar * self._hop_size_bars
    hop_size_seconds = 60.0 * hop_size_quarters / quarters_per_minute

    # Split note sequence by bar hop size (in seconds).
    subsequences = sequences_lib.split_note_sequence(
        note_sequence, hop_size_seconds)

    if self._first_subsequence_only and len(subsequences) > 1:
      return data.ConverterTensors()

    sequence_tensors = []
    sequence_chord_tensors = []

    for subsequence in subsequences:
      # Quantize this subsequence.
      try:
        quantized_subsequence = mm.quantize_note_sequence(
            subsequence, self._steps_per_quarter)
        if (mm.steps_per_bar_in_quantized_sequence(quantized_subsequence) !=
            self._steps_per_bar):
          return data.ConverterTensors()
      except (mm.BadTimeSignatureError, mm.NonIntegerStepsPerBarError,
              mm.NegativeTimeError):
        return data.ConverterTensors()

      # Convert the quantized subsequence to tensors.
      tensors, chord_tensors = self._quantized_subsequence_to_tensors(
          quantized_subsequence)
      if tensors:
        sequence_tensors.append(tensors)
        if self._chord_encoding:
          sequence_chord_tensors.append(chord_tensors)

    return data.ConverterTensors(
        inputs=sequence_tensors, outputs=sequence_tensors,
        controls=sequence_chord_tensors)
Beispiel #12
0
  def process(self, kv):
    # Seed random number generator based on key so that hop times are
    # deterministic.
    key, ns_str = kv
    m = hashlib.md5(key)
    random.seed(int(m.hexdigest(), 16))

    # Deserialize NoteSequence proto.
    ns = music_pb2.NoteSequence.FromString(ns_str)

    # Apply sustain pedal.
    ns = sequences_lib.apply_sustain_control_changes(ns)

    # Remove control changes as there are potentially a lot of them and they are
    # no longer needed.
    del ns.control_changes[:]

    if (self._min_hop_size_seconds and
        ns.total_time < self._min_hop_size_seconds):
      Metrics.counter('extract_examples', 'sequence_too_short').inc()
      return

    sequences = []
    for _ in range(self._num_replications):
      if self._max_hop_size_seconds:
        if self._max_hop_size_seconds == self._min_hop_size_seconds:
          # Split using fixed hop size.
          sequences += sequences_lib.split_note_sequence(
              ns, self._max_hop_size_seconds)
        else:
          # Sample random hop positions such that each segment size is within
          # the specified range.
          hop_times = [0.0]
          while hop_times[-1] <= ns.total_time - self._min_hop_size_seconds:
            if hop_times[-1] + self._max_hop_size_seconds < ns.total_time:
              # It's important that we get a valid hop size here, since the
              # remainder of the sequence is too long.
              max_offset = min(
                  self._max_hop_size_seconds,
                  ns.total_time - self._min_hop_size_seconds - hop_times[-1])
            else:
              # It's okay if the next hop time is invalid (in which case we'll
              # just stop).
              max_offset = self._max_hop_size_seconds
            offset = random.uniform(self._min_hop_size_seconds, max_offset)
            hop_times.append(hop_times[-1] + offset)
          # Split at the chosen hop times (ignoring zero and the final invalid
          # time).
          sequences += sequences_lib.split_note_sequence(ns, hop_times[1:-1])
      else:
        sequences += [ns]

    for performance_sequence in sequences:
      if self._encode_score_fns:
        # We need to extract a score.
        if not self._absolute_timing:
          # Beats are required to extract a score with metric timing.
          beats = [
              ta for ta in performance_sequence.text_annotations
              if (ta.annotation_type ==
                  music_pb2.NoteSequence.TextAnnotation.BEAT)
              and ta.time <= performance_sequence.total_time
          ]
          if len(beats) < 2:
            Metrics.counter('extract_examples', 'not_enough_beats').inc()
            continue

          # Ensure the sequence starts and ends on a beat.
          performance_sequence = sequences_lib.extract_subsequence(
              performance_sequence,
              start_time=min(beat.time for beat in beats),
              end_time=max(beat.time for beat in beats)
          )

          # Infer beat-aligned chords (only for relative timing).
          try:
            chord_inference.infer_chords_for_sequence(
                performance_sequence,
                chord_change_prob=0.25,
                chord_note_concentration=50.0,
                add_key_signatures=True)
          except chord_inference.ChordInferenceError:
            Metrics.counter('extract_examples', 'chord_inference_failed').inc()
            continue

        # Infer melody regardless of relative/absolute timing.
        try:
          melody_instrument = melody_inference.infer_melody_for_sequence(
              performance_sequence,
              melody_interval_scale=2.0,
              rest_prob=0.1,
              instantaneous_non_max_pitch_prob=1e-15,
              instantaneous_non_empty_rest_prob=0.0,
              instantaneous_missing_pitch_prob=1e-15)
        except melody_inference.MelodyInferenceError:
          Metrics.counter('extract_examples', 'melody_inference_failed').inc()
          continue

        if not self._absolute_timing:
          # Now rectify detected beats to occur at fixed tempo.
          # TODO(iansimon): also include the alignment
          score_sequence, unused_alignment = sequences_lib.rectify_beats(
              performance_sequence, beats_per_minute=SCORE_BPM)
        else:
          # Score uses same timing as performance.
          score_sequence = copy.deepcopy(performance_sequence)

        # Remove melody notes from performance.
        performance_notes = []
        for note in performance_sequence.notes:
          if note.instrument != melody_instrument:
            performance_notes.append(note)
        del performance_sequence.notes[:]
        performance_sequence.notes.extend(performance_notes)

        # Remove non-melody notes from score.
        score_notes = []
        for note in score_sequence.notes:
          if note.instrument == melody_instrument:
            score_notes.append(note)
        del score_sequence.notes[:]
        score_sequence.notes.extend(score_notes)

        # Remove key signatures and beat/chord annotations from performance.
        del performance_sequence.key_signatures[:]
        del performance_sequence.text_annotations[:]

        Metrics.counter('extract_examples', 'extracted_score').inc()

      for augment_fn in self._augment_fns:
        # Augment and encode the performance.
        try:
          augmented_performance_sequence = augment_fn(performance_sequence)
        except DataAugmentationError:
          Metrics.counter(
              'extract_examples', 'augment_performance_failed').inc()
          continue
        example_dict = {
            'targets': self._encode_performance_fn(
                augmented_performance_sequence)
        }
        if not example_dict['targets']:
          Metrics.counter('extract_examples', 'skipped_empty_targets').inc()
          continue

        if self._encode_score_fns:
          # Augment the extracted score.
          try:
            augmented_score_sequence = augment_fn(score_sequence)
          except DataAugmentationError:
            Metrics.counter('extract_examples', 'augment_score_failed').inc()
            continue

          # Apply all score encoding functions.
          skip = False
          for name, encode_score_fn in self._encode_score_fns.items():
            example_dict[name] = encode_score_fn(augmented_score_sequence)
            if not example_dict[name]:
              Metrics.counter('extract_examples',
                              'skipped_empty_%s' % name).inc()
              skip = True
              break
          if skip:
            continue

        Metrics.counter('extract_examples', 'encoded_example').inc()
        Metrics.distribution(
            'extract_examples', 'performance_length_in_seconds').update(
                int(augmented_performance_sequence.total_time))

        yield generator_utils.to_example(example_dict)
Beispiel #13
0
    def process_midi(self, f):
        def augment_note_sequence(ns, stretch_factor, transpose_amount):
            """Augment a NoteSequence by time stretch and pitch transposition."""
            augmented_ns = sequences_lib.stretch_note_sequence(ns,
                                                               stretch_factor,
                                                               in_place=False)
            try:
                _, num_deleted_notes = sequences_lib.transpose_note_sequence(
                    augmented_ns,
                    transpose_amount,
                    min_allowed_pitch=MIN_PITCH,
                    max_allowed_pitch=MAX_PITCH,
                    in_place=True)
            except chord_symbols_lib.ChordSymbolError:
                raise datagen_beam.DataAugmentationError(
                    'Transposition of chord symbol(s) failed.')
            if num_deleted_notes:
                raise datagen_beam.DataAugmentationError(
                    'Transposition caused out-of-range pitch(es).')
            return augmented_ns

        self._min_hop_size_seconds = 0.0
        self._max_hop_size_seconds = 0.0
        self._num_replications = 1
        self._encode_performance_fn = self.performance_encoder(
        ).encode_note_sequence
        self._encode_score_fns = dict(
            (name, encoder.encode_note_sequence)
            for name, encoder in self.score_encoders())

        augment_params = itertools.product(self.stretch_factors,
                                           self.transpose_amounts)
        augment_fns = [
            functools.partial(augment_note_sequence,
                              stretch_factor=s,
                              transpose_amount=t) for s, t in augment_params
        ]

        self._augment_fns = augment_fns
        self._absolute_timing = self.absolute_timing
        self._random_crop_length = self.random_crop_length_in_datagen
        if self._random_crop_length is not None:
            self._augment_fns = self._augment_fns

        rets = []
        ns = magenta.music.midi_file_to_sequence_proto(f)
        # Apply sustain pedal.
        ns = sequences_lib.apply_sustain_control_changes(ns)

        # Remove control changes as there are potentially a lot of them and they are
        # no longer needed.
        del ns.control_changes[:]

        if (self._min_hop_size_seconds
                and ns.total_time < self._min_hop_size_seconds):
            print("sequence_too_short")
            return []

        sequences = []
        for _ in range(self._num_replications):
            if self._max_hop_size_seconds:
                if self._max_hop_size_seconds == self._min_hop_size_seconds:
                    # Split using fixed hop size.
                    sequences += sequences_lib.split_note_sequence(
                        ns, self._max_hop_size_seconds)
                else:
                    # Sample random hop positions such that each segment size is within
                    # the specified range.
                    hop_times = [0.0]
                    while hop_times[
                            -1] <= ns.total_time - self._min_hop_size_seconds:
                        if hop_times[
                                -1] + self._max_hop_size_seconds < ns.total_time:
                            # It's important that we get a valid hop size here, since the
                            # remainder of the sequence is too long.
                            max_offset = min(
                                self._max_hop_size_seconds, ns.total_time -
                                self._min_hop_size_seconds - hop_times[-1])
                        else:
                            # It's okay if the next hop time is invalid (in which case we'll
                            # just stop).
                            max_offset = self._max_hop_size_seconds
                        offset = random.uniform(self._min_hop_size_seconds,
                                                max_offset)
                        hop_times.append(hop_times[-1] + offset)
                    # Split at the chosen hop times (ignoring zero and the final invalid
                    # time).
                    sequences += sequences_lib.split_note_sequence(
                        ns, hop_times[1:-1])
            else:
                sequences += [ns]

        for performance_sequence in sequences:
            if self._encode_score_fns:
                # We need to extract a score.
                if not self._absolute_timing:
                    # Beats are required to extract a score with metric timing.
                    beats = [
                        ta for ta in performance_sequence.text_annotations
                        if (ta.annotation_type ==
                            music_pb2.NoteSequence.TextAnnotation.BEAT)
                        and ta.time <= performance_sequence.total_time
                    ]
                    if len(beats) < 2:
                        print('not_enough_beats')
                        continue

                    # Ensure the sequence starts and ends on a beat.
                    performance_sequence = sequences_lib.extract_subsequence(
                        performance_sequence,
                        start_time=min(beat.time for beat in beats),
                        end_time=max(beat.time for beat in beats))

                    # Infer beat-aligned chords (only for relative timing).
                    try:
                        chord_inference.infer_chords_for_sequence(
                            performance_sequence,
                            chord_change_prob=0.25,
                            chord_note_concentration=50.0,
                            add_key_signatures=True)
                    except chord_inference.ChordInferenceError:
                        print("chord_inference_failed")
                        continue

                # Infer melody regardless of relative/absolute timing.
                try:
                    melody_instrument = melody_inference.infer_melody_for_sequence(
                        performance_sequence,
                        melody_interval_scale=2.0,
                        rest_prob=0.1,
                        instantaneous_non_max_pitch_prob=1e-15,
                        instantaneous_non_empty_rest_prob=0.0,
                        instantaneous_missing_pitch_prob=1e-15)
                except melody_inference.MelodyInferenceError:
                    print('melody_inference_failed')
                    continue

                if not self._absolute_timing:
                    # Now rectify detected beats to occur at fixed tempo.
                    # TODO(iansimon): also include the alignment
                    score_sequence, unused_alignment = sequences_lib.rectify_beats(
                        performance_sequence, beats_per_minute=SCORE_BPM)
                else:
                    # Score uses same timing as performance.
                    score_sequence = copy.deepcopy(performance_sequence)

                # Remove melody notes from performance.
                performance_notes = []
                for note in performance_sequence.notes:
                    if note.instrument != melody_instrument:
                        performance_notes.append(note)
                del performance_sequence.notes[:]
                performance_sequence.notes.extend(performance_notes)

                # Remove non-melody notes from score.
                score_notes = []
                for note in score_sequence.notes:
                    if note.instrument == melody_instrument:
                        score_notes.append(note)
                del score_sequence.notes[:]
                score_sequence.notes.extend(score_notes)

                # Remove key signatures and beat/chord annotations from performance.
                del performance_sequence.key_signatures[:]
                del performance_sequence.text_annotations[:]

            for augment_fn in self._augment_fns:
                # Augment and encode the performance.
                try:
                    augmented_performance_sequence = augment_fn(
                        performance_sequence)
                except DataAugmentationError as e:
                    print("augment_performance_failed", e)
                    continue
                example_dict = {
                    'targets':
                    self._encode_performance_fn(augmented_performance_sequence)
                }
                if not example_dict['targets']:
                    print('skipped_empty_targets')
                    continue

                if (self._random_crop_length and len(example_dict['targets']) >
                        self._random_crop_length):
                    # Take a random crop of the encoded performance.
                    max_offset = len(
                        example_dict['targets']) - self._random_crop_length
                    offset = random.randrange(max_offset + 1)
                    example_dict['targets'] = example_dict['targets'][
                        offset:offset + self._random_crop_length]

                if self._encode_score_fns:
                    # Augment the extracted score.
                    try:
                        augmented_score_sequence = augment_fn(score_sequence)
                    except DataAugmentationError:
                        print('augment_score_failed')
                        continue

                    # Apply all score encoding functions.
                    skip = False
                    for name, encode_score_fn in self._encode_score_fns.items(
                    ):
                        example_dict[name] = encode_score_fn(
                            augmented_score_sequence)
                        if not example_dict[name]:
                            print('skipped_empty_%s' % name)
                            skip = True
                            break
                    if skip:
                        continue

                rets.append(example_dict)
        return rets
Beispiel #14
0
  def testSplitNoteSequenceWithHopSize(self):
    # Tests splitting a NoteSequence at regular hop size, truncating notes.
    sequence = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        sequence, 0,
        [(12, 100, 0.01, 8.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.50),
         (55, 120, 4.0, 4.01), (52, 99, 4.75, 5.0)])
    testing_lib.add_chords_to_sequence(
        sequence, [('C', 1.0), ('G7', 2.0), ('F', 4.0)])

    expected_subsequence_1 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_1, 0,
        [(12, 100, 0.01, 3.0), (11, 55, 0.22, 0.50), (40, 45, 2.50, 3.0)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_1, [('C', 1.0), ('G7', 2.0)])
    expected_subsequence_1.total_time = 3.0
    expected_subsequence_1.subsequence_info.end_time_offset = 5.0

    expected_subsequence_2 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_track_to_sequence(
        expected_subsequence_2, 0,
        [(55, 120, 1.0, 1.01), (52, 99, 1.75, 2.0)])
    testing_lib.add_chords_to_sequence(
        expected_subsequence_2, [('G7', 0.0), ('F', 1.0)])
    expected_subsequence_2.total_time = 2.0
    expected_subsequence_2.subsequence_info.start_time_offset = 3.0
    expected_subsequence_2.subsequence_info.end_time_offset = 3.0

    expected_subsequence_3 = common_testing_lib.parse_test_proto(
        music_pb2.NoteSequence,
        """
        time_signatures: {
          numerator: 4
          denominator: 4}
        tempos: {
          qpm: 60}""")
    testing_lib.add_chords_to_sequence(
        expected_subsequence_3, [('F', 0.0)])
    expected_subsequence_3.total_time = 0.0
    expected_subsequence_3.subsequence_info.start_time_offset = 6.0
    expected_subsequence_3.subsequence_info.end_time_offset = 2.0

    subsequences = sequences_lib.split_note_sequence(
        sequence, hop_size_seconds=3.0)
    self.assertEquals(3, len(subsequences))
    self.assertProtoEquals(expected_subsequence_1, subsequences[0])
    self.assertProtoEquals(expected_subsequence_2, subsequences[1])
    self.assertProtoEquals(expected_subsequence_3, subsequences[2])