def infer_dense_chords_for_sequence( sequence, instrument=None, min_notes_per_chord=3): """Infers chords for a NoteSequence and adds them as TextAnnotations. For each set of simultaneously-active notes in a NoteSequence (optionally for only one instrument), infers a chord symbol and adds it to NoteSequence as a TextAnnotation. Every change in the set of active notes will result in a new chord symbol unless the new set is smaller than `min_notes_per_chord`. If `sequence` is quantized, simultaneity will be determined by quantized steps instead of time. Not to be confused with the chord inference in magenta.music.chord_inference that attempts to infer a more natural chord sequence with changes at regular metric intervals. Args: sequence: The NoteSequence for which chords will be inferred. Will be modified in place. instrument: The instrument number whose notes will be used for chord inference. If None, all instruments will be used. min_notes_per_chord: The minimum number of simultaneous notes for which to infer a chord. Raises: ChordSymbolException: If a chord cannot be determined for a set of simultaneous notes in `sequence`. """ notes = [note for note in sequence.notes if not note.is_drum and (instrument is None or note.instrument == instrument)] sorted_notes = sorted(notes, key=lambda note: note.start_time) # If the sequence is quantized, use quantized steps instead of time. if is_quantized_sequence(sequence): note_start = lambda note: note.quantized_start_step note_end = lambda note: note.quantized_end_step else: note_start = lambda note: note.start_time note_end = lambda note: note.end_time # Sort all note start and end events. onsets = [(note_start(note), idx, False) for idx, note in enumerate(sorted_notes)] offsets = [(note_end(note), idx, True) for idx, note in enumerate(sorted_notes)] events = sorted(onsets + offsets) current_time = 0 current_figure = constants.NO_CHORD active_notes = set() for time, idx, is_offset in events: if time > current_time: active_pitches = set(sorted_notes[idx].pitch for idx in active_notes) if len(active_pitches) >= min_notes_per_chord: # Infer a chord symbol for the active pitches. figure = chord_symbols_lib.pitches_to_chord_symbol(active_pitches) if figure != current_figure: # Add a text annotation to the sequence. text_annotation = sequence.text_annotations.add() text_annotation.text = figure text_annotation.annotation_type = CHORD_SYMBOL if is_quantized_sequence(sequence): text_annotation.time = ( current_time * sequence.quantization_info.steps_per_quarter) text_annotation.quantized_step = current_time else: text_annotation.time = current_time current_figure = figure current_time = time if is_offset: active_notes.remove(idx) else: active_notes.add(idx) assert not active_notes
def testPitchesToChordSymbol(self): # Test basic triads. figure = chord_symbols_lib.pitches_to_chord_symbol([60, 64, 67]) self.assertEqual('C', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([45, 48, 52]) self.assertEqual('Am', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([63, 66, 69]) self.assertEqual('Ebo', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([71, 75, 79]) self.assertEqual('B+', figure) # Test basic inversions. figure = chord_symbols_lib.pitches_to_chord_symbol([59, 62, 67]) self.assertEqual('G/B', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([65, 70, 73]) self.assertEqual('Bbm/F', figure) # Test suspended chords. figure = chord_symbols_lib.pitches_to_chord_symbol([62, 67, 69]) self.assertEqual('Dsus', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([55, 60, 62, 65]) self.assertEqual('Gsus7', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([67, 69, 74]) self.assertEqual('Gsus2', figure) # Test more complex chords. figure = chord_symbols_lib.pitches_to_chord_symbol([45, 46, 50, 53]) self.assertEqual('Bbmaj7/A', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [63, 67, 70, 72, 74]) self.assertEqual('Cm9/Eb', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [53, 60, 64, 67, 70]) self.assertEqual('C7/F', figure) # Test chords with modifications. figure = chord_symbols_lib.pitches_to_chord_symbol( [67, 71, 72, 74, 77]) self.assertEqual('G7(add4)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [64, 68, 71, 74, 79]) self.assertEqual('E7(#9)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol([60, 62, 64, 67]) self.assertEqual('C(add2)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [60, 64, 68, 70, 75]) self.assertEqual('C+7(#9)', figure) # Test invalid chord. with self.assertRaises(chord_symbols_lib.ChordSymbolError): chord_symbols_lib.pitches_to_chord_symbol( [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71])
def infer_chords_for_sequence(sequence, instrument=None, min_notes_per_chord=3): """Infers chords for a NoteSequence and adds them as TextAnnotations. For each set of simultaneously-active notes in a NoteSequence (optionally for only one instrument), infers a chord symbol and adds it to NoteSequence as a TextAnnotation. Every change in the set of active notes will result in a new chord symbol unless the new set is smaller than `min_notes_per_chord`. If `sequence` is quantized, simultaneity will be determined by quantized steps instead of time. Args: sequence: The NoteSequence for which chords will be inferred. Will be modified in place. instrument: The instrument number whose notes will be used for chord inference. If None, all instruments will be used. min_notes_per_chord: The minimum number of simultaneous notes for which to infer a chord. Raises: ChordSymbolException: If a chord cannot be determined for a set of simultaneous notes in `sequence`. """ notes = [ note for note in sequence.notes if not note.is_drum and ( instrument is None or note.instrument == instrument) ] sorted_notes = sorted(notes, key=lambda note: note.start_time) # If the sequence is quantized, use quantized steps instead of time. if is_quantized_sequence(sequence): note_start = lambda note: note.quantized_start_step note_end = lambda note: note.quantized_end_step else: note_start = lambda note: note.start_time note_end = lambda note: note.end_time # Sort all note start and end events. onsets = [(note_start(note), idx, False) for idx, note in enumerate(sorted_notes)] offsets = [(note_end(note), idx, True) for idx, note in enumerate(sorted_notes)] events = sorted(onsets + offsets) current_time = 0 current_figure = constants.NO_CHORD active_notes = set() for time, idx, is_offset in events: if time > current_time: active_pitches = set(sorted_notes[idx].pitch for idx in active_notes) if len(active_pitches) >= min_notes_per_chord: # Infer a chord symbol for the active pitches. figure = chord_symbols_lib.pitches_to_chord_symbol( active_pitches) if figure != current_figure: # Add a text annotation to the sequence. text_annotation = sequence.text_annotations.add() text_annotation.text = figure text_annotation.annotation_type = CHORD_SYMBOL if is_quantized_sequence(sequence): text_annotation.time = ( current_time * sequence.quantization_info.steps_per_quarter) text_annotation.quantized_step = current_time else: text_annotation.time = current_time current_figure = figure current_time = time if is_offset: active_notes.remove(idx) else: active_notes.add(idx) assert not active_notes
def testPitchesToChordSymbol(self): # Test basic triads. figure = chord_symbols_lib.pitches_to_chord_symbol( [60, 64, 67]) self.assertEqual('C', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [45, 48, 52]) self.assertEqual('Am', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [63, 66, 69]) self.assertEqual('Ebo', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [71, 75, 79]) self.assertEqual('B+', figure) # Test basic inversions. figure = chord_symbols_lib.pitches_to_chord_symbol( [59, 62, 67]) self.assertEqual('G/B', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [65, 70, 73]) self.assertEqual('Bbm/F', figure) # Test suspended chords. figure = chord_symbols_lib.pitches_to_chord_symbol( [62, 67, 69]) self.assertEqual('Dsus', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [55, 60, 62, 65]) self.assertEqual('Gsus7', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [67, 69, 74]) self.assertEqual('Gsus2', figure) # Test more complex chords. figure = chord_symbols_lib.pitches_to_chord_symbol( [45, 46, 50, 53]) self.assertEqual('Bbmaj7/A', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [63, 67, 70, 72, 74]) self.assertEqual('Cm9/Eb', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [53, 60, 64, 67, 70]) self.assertEqual('C7/F', figure) # Test chords with modifications. figure = chord_symbols_lib.pitches_to_chord_symbol( [67, 71, 72, 74, 77]) self.assertEqual('G7(add4)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [64, 68, 71, 74, 79]) self.assertEqual('E7(#9)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [60, 62, 64, 67]) self.assertEqual('C(add2)', figure) figure = chord_symbols_lib.pitches_to_chord_symbol( [60, 64, 68, 70, 75]) self.assertEqual('C+7(#9)', figure) # Test invalid chord. with self.assertRaises(chord_symbols_lib.ChordSymbolException): chord_symbols_lib.pitches_to_chord_symbol( [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71])