def testSimpleSequenceToPrettyMidi_DropEventsAfterLastNote(self): source_midi = pretty_midi.PrettyMIDI(self.midi_simple_filename) multi_tempo_sequence_proto = midi_io.midi_to_sequence_proto(source_midi) # Add a final tempo long after the last note. multi_tempo_sequence_proto.tempos.add(time=600.0, qpm=120) # Translate without dropping. translated_midi = midi_io.sequence_proto_to_pretty_midi( multi_tempo_sequence_proto) self.CheckPrettyMidiAndSequence(translated_midi, multi_tempo_sequence_proto) # Translate dropping anything after the last note. translated_midi = midi_io.sequence_proto_to_pretty_midi( multi_tempo_sequence_proto, drop_events_n_seconds_after_last_note=0) # The added tempo should have been dropped. del multi_tempo_sequence_proto.tempos[-1] self.CheckPrettyMidiAndSequence(translated_midi, multi_tempo_sequence_proto) # Add a final tempo 15 seconds after the last note. last_note_time = max([n.end_time for n in multi_tempo_sequence_proto.notes]) multi_tempo_sequence_proto.tempos.add(time=last_note_time + 15, qpm=120) # Translate dropping anything 30 seconds after the last note, which should # preserve the added tempo. translated_midi = midi_io.sequence_proto_to_pretty_midi( multi_tempo_sequence_proto, drop_events_n_seconds_after_last_note=30) self.CheckPrettyMidiAndSequence(translated_midi, multi_tempo_sequence_proto)
def testEmptySequenceToPrettyMidi_DropEventsAfterLastNote(self): source_sequence = music_pb2.NoteSequence() # Translate without dropping. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence) self.assertEqual(1, len(translated_midi.instruments)) self.assertEqual(0, len(translated_midi.instruments[0].notes)) # Translate dropping anything after 30 seconds. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence, drop_events_n_seconds_after_last_note=30) self.assertEqual(1, len(translated_midi.instruments)) self.assertEqual(0, len(translated_midi.instruments[0].notes))
def testNonEmptySequenceWithNoNotesToPrettyMidi_DropEventsAfterLastNote(self): source_sequence = music_pb2.NoteSequence() source_sequence.tempos.add(time=0, qpm=120) source_sequence.tempos.add(time=10, qpm=160) source_sequence.tempos.add(time=40, qpm=240) # Translate without dropping. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence) self.CheckPrettyMidiAndSequence(translated_midi, source_sequence) # Translate dropping anything after 30 seconds. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence, drop_events_n_seconds_after_last_note=30) del source_sequence.tempos[-1] self.CheckPrettyMidiAndSequence(translated_midi, source_sequence)
def testNonEmptySequenceWithNoNotesToPrettyMidi_DropEventsAfterLastNote( self): source_sequence = music_pb2.NoteSequence() source_sequence.tempos.add(time=0, qpm=120) source_sequence.tempos.add(time=10, qpm=160) source_sequence.tempos.add(time=40, qpm=240) # Translate without dropping. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence) self.CheckPrettyMidiAndSequence(translated_midi, source_sequence) # Translate dropping anything after 30 seconds. translated_midi = midi_io.sequence_proto_to_pretty_midi( source_sequence, drop_events_n_seconds_after_last_note=30) del source_sequence.tempos[-1] self.CheckPrettyMidiAndSequence(translated_midi, source_sequence)
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 testSimpleSequenceToPrettyMidi_MultipleTempos(self): source_midi = pretty_midi.PrettyMIDI(self.midi_simple_filename) multi_tempo_sequence_proto = midi_io.midi_to_sequence_proto(source_midi) multi_tempo_sequence_proto.tempos.add(time=1.0, qpm=60) multi_tempo_sequence_proto.tempos.add(time=2.0, qpm=120) translated_midi = midi_io.sequence_proto_to_pretty_midi( multi_tempo_sequence_proto) self.CheckPrettyMidiAndSequence(translated_midi, multi_tempo_sequence_proto)
def testSimpleSequenceToPrettyMidi_DefaultTicksAndTempo(self): source_midi = pretty_midi.PrettyMIDI(self.midi_simple_filename) stripped_sequence_proto = midi_io.midi_to_sequence_proto(source_midi) del stripped_sequence_proto.tempos[:] stripped_sequence_proto.ClearField('ticks_per_quarter') expected_sequence_proto = music_pb2.NoteSequence() expected_sequence_proto.CopyFrom(stripped_sequence_proto) expected_sequence_proto.tempos.add( qpm=constants.DEFAULT_QUARTERS_PER_MINUTE) expected_sequence_proto.ticks_per_quarter = constants.STANDARD_PPQ translated_midi = midi_io.sequence_proto_to_pretty_midi( stripped_sequence_proto) self.CheckPrettyMidiAndSequence(translated_midi, expected_sequence_proto)
def synthesize(sequence, sample_rate, wave=np.sin): """Synthesizes audio from a music_pb2.NoteSequence using a waveform. This uses the pretty_midi `synthesize` method. Sound quality will be lower than using `fluidsynth` with a good SoundFont. Args: sequence: A music_pb2.NoteSequence to synthesize. sample_rate: An integer audio sampling rate in Hz. wave: Function that returns a periodic waveform. Returns: A 1-D numpy float array containing the synthesized waveform. """ midi = midi_io.sequence_proto_to_pretty_midi(sequence) return midi.synthesize(fs=sample_rate, wave=wave)
def fluidsynth(sequence, sample_rate, sf2_path=None): """Synthesizes audio from a music_pb2.NoteSequence using FluidSynth. This uses the pretty_midi `fluidsynth` method. In order to use this synth, you must have FluidSynth and pyFluidSynth installed. Args: sequence: A music_pb2.NoteSequence to synthesize. sample_rate: An integer audio sampling rate in Hz. sf2_path: A string path to a SoundFont. If None, uses the TimGM6mb.sf2 file included with pretty_midi. Returns: A 1-D numpy float array containing the synthesized waveform. """ midi = midi_io.sequence_proto_to_pretty_midi(sequence) return midi.fluidsynth(fs=sample_rate, sf2_path=sf2_path)
def testSimpleSequenceToPrettyMidi_FirstTempoNotAtZero(self): source_midi = pretty_midi.PrettyMIDI(self.midi_simple_filename) multi_tempo_sequence_proto = midi_io.midi_to_sequence_proto(source_midi) del multi_tempo_sequence_proto.tempos[:] multi_tempo_sequence_proto.tempos.add(time=1.0, qpm=60) multi_tempo_sequence_proto.tempos.add(time=2.0, qpm=120) translated_midi = midi_io.sequence_proto_to_pretty_midi( multi_tempo_sequence_proto) # Translating to MIDI adds an implicit DEFAULT_QUARTERS_PER_MINUTE tempo # at time 0, so recreate the list with that in place. del multi_tempo_sequence_proto.tempos[:] multi_tempo_sequence_proto.tempos.add( time=0.0, qpm=constants.DEFAULT_QUARTERS_PER_MINUTE) multi_tempo_sequence_proto.tempos.add(time=1.0, qpm=60) multi_tempo_sequence_proto.tempos.add(time=2.0, qpm=120) self.CheckPrettyMidiAndSequence(translated_midi, multi_tempo_sequence_proto)
def testInstrumentInfo_NoteSequenceToPrettyMidi(self): source_sequence = music_pb2.NoteSequence() source_sequence.notes.add( pitch=60, start_time=0.0, end_time=0.5, velocity=80, instrument=0) source_sequence.notes.add( pitch=60, start_time=0.5, end_time=1.0, velocity=80, instrument=1) instrument_info1 = source_sequence.instrument_infos.add() instrument_info1.name = 'inst_0' instrument_info1.instrument = 0 instrument_info2 = source_sequence.instrument_infos.add() instrument_info2.name = 'inst_1' instrument_info2.instrument = 1 translated_midi = midi_io.sequence_proto_to_pretty_midi(source_sequence) translated_sequence = midi_io.midi_to_note_sequence(translated_midi) self.assertEqual( len(source_sequence.instrument_infos), len(translated_sequence.instrument_infos)) self.assertEqual(source_sequence.instrument_infos[0].name, translated_sequence.instrument_infos[0].name) self.assertEqual(source_sequence.instrument_infos[1].name, translated_sequence.instrument_infos[1].name)
def CheckSequenceToPrettyMidi(self, filename): """Test the translation from Sequence proto to PrettyMIDI.""" source_midi = pretty_midi.PrettyMIDI(filename) sequence_proto = midi_io.midi_to_sequence_proto(source_midi) translated_midi = midi_io.sequence_proto_to_pretty_midi(sequence_proto) self.CheckPrettyMidiAndSequence(translated_midi, sequence_proto)
def get_downbeats(sequence): downbeats = midi_io.sequence_proto_to_pretty_midi(sequence).get_downbeats() return [d for d in downbeats if d < sequence.total_time]