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
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 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)
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)
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)
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 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
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])