def from_harte_chord_string(cls, chord_string): """ Create a chord from a chord string in the syntax proposed in the 2005 paper 'SYMBOLIC REPRESENTATION OF MUSICAL CHORDS: A PROPOSED SYNTAX FOR TEXT ANNOTATIONS' by Harte et al. :param chord_string: Chord string in syntax proposed by Harte et al. :return: Chord type as specified by the chord string >>> Chord.from_harte_chord_string('C') == Chord.from_harte_chord_string('C:(3,5)') True >>> Chord.from_harte_chord_string('C:maj(4)') == Chord.from_harte_chord_string('C:(3,4,5)') True """ # First find non-chord strings if chord_string == 'N': return None # Extract root, shorthand, degree_list and bass_degree from the chord string match_chord_string = re.search( r'(?P<root>[ABCDEFG][b#]{0,2}):?' r'(?P<shorthand>minmaj7|hdim7|maj7|min7|dim7|maj6|min6|maj9|min9|sus9|7|9|maj|min|dim|aug)?' r'(\((?P<degree_list>\*?[#b]{0,2}[0-9]{1,2}(, ?\*?[#b]{0,2}[0-9]{1,2})*)\))?' r'(/(?P<bass_degree>[#b]{0,2}[0-9]{1,2}))?', chord_string ) # Any chord string needs to have a root note if match_chord_string.group('root') is None: return None root_note_pitch_class = PitchClass.from_harte_pitch_class(match_chord_string.group('root')) # Find the shorthand if match_chord_string.group('shorthand') is None: if match_chord_string.group('degree_list') is None: # This is a major chord shorthand_str = 'maj' else: # The chord type is specified by the degree list shorthand_str = '' else: # We use a predefined shorthand shorthand_str = match_chord_string.group('shorthand') # Find the degrees if match_chord_string.group('degree_list') is None: degree_str_list = [] else: degree_str_list = match_chord_string.group('degree_list').split(',') # Find the bass degree if match_chord_string.group('bass_degree') is None: bass_degree = Interval(0) else: bass_degree = Interval.from_harte_interval(match_chord_string.group('bass_degree')) # Return the chord return cls.from_shorthand_degree_bass(root_note_pitch_class, shorthand_str, degree_str_list, bass_degree)
def _midi_pitch_to_harte_class(self): """ Get the harte class from the midi pitch of our Pitch object :return: Harte class from the midi pitch of our Pitch object >>> Pitch(69)._midi_pitch_to_harte_class() ['G##', 'A', 'Bbb'] """ return PitchClass(self.midi_pitch % 12).harte_pitch_class
def _midi_pitch_to_pitch_class(self): """ Get the pitch class from the midi pitch of our Pitch object :return: Pitch class from the midi pitch of our Pitch object >>> str(Pitch(69)._midi_pitch_to_pitch_class()) 'A' """ return PitchClass(self.midi_pitch % 12)
def add_tab_block(self, tab_block_str: [Line]): """ Process the content of a tab block (6 subsequent lines of LineType Tablature), add chords to self.chords :param tab_block_str: Six subsequent Lines of the LineType Tablature """ smallest_line_length = min( [len(block_line_str) for block_line_str in tab_block_str]) for chord_x in range(0, smallest_line_length): # Read all six strings together per x-coordinate finger_list = [] for tab_block_line_str in reversed(tab_block_str): finger_list.append(tab_block_line_str[chord_x]) fingers = ''.join(finger_list) if re.match(r'[x0-9]{6}', fingers) and fingers != 'xxxxxx': # A chord sounds at this x position! fingering = Fingering('1', fingers[0], fingers[1], fingers[2], fingers[3], fingers[4], fingers[5]) fingering_chroma = fingering.get_extended_chroma_vector() # Find nearest chord from vocabulary smallest_distance = 2 best_matching_chord_str = 'X' for chord_template in ALL_CHORDS_LIST.chord_templates: cosine_distance = ssd.cosine(fingering_chroma[:12], chord_template.chroma_list) if cosine_distance < smallest_distance: smallest_distance = cosine_distance best_matching_chord_str = str( PitchClass( chord_template.key))[0] + chord_template.mode chord = Chord.from_common_tab_notation_string( best_matching_chord_str) # Fix bass note bass_note = PitchClass(fingering_chroma[12]) bass_interval = Interval.from_pitch_class_distances( chord.root_note, bass_note) chord.bass_degree = bass_interval # Add to self.chords self.chords.append((chord_x, chord))
def from_common_tab_notation_string(cls, chord_string): """ Create a chord from a string as it is typically found in a tab file :param chord_string: Chord string as you typically find it in a tab file :return: Chord as specified by the chord string >>> str(Chord.from_common_tab_notation_string('C:min')) 'None' >>> str(Chord.from_common_tab_notation_string('Cmaj')) 'C' >>> str(Chord.from_common_tab_notation_string('Db11')) 'C#:(2,3,4,5,b7)' """ chord_parts = re.search( r'(?P<root_str>[ABCDEFG][b#]?)' r'(?P<chord_type_str>7sus4|7sus|sus2|sus4|sus|m6|6/9|6|7-5|7\+5|5|m9|maj9|add9|9|11|13|maj7|M7|maj|min7|m7' r'|m|dim|aug|\+|7)?' r'(/(?P<bass_note_str>[ABCDEFG][b#])?)?$', chord_string) # This string was not recognized as a chord if chord_parts is None: return None # Root note root_note_pitch_class = PitchClass.from_harte_pitch_class(chord_parts.group('root_str')) # Chord type chord_type_string = chord_parts.group('chord_type_str') if chord_type_string is None: chord_type_string = 'maj' # Bass note bass_note_string = chord_parts.group('bass_note_str') if bass_note_string is None: bass_interval = Interval(0) else: bass_note_pitch_class = PitchClass.from_harte_pitch_class(bass_note_string) bass_interval = Interval.from_pitch_class_distances(root_note_pitch_class, bass_note_pitch_class) return Chord.from_shorthand_degree_bass(root_note_pitch_class, chord_type_string, [], bass_interval, 'tab')
def add_note(self, note: pretty_midi.Note): """ Add a Note to the Event :param note: The Note we add to the Event """ self.notes.append(note) self.pitches.add(Pitch(note.pitch)) pitch_class_nr = note.pitch % 12 self.pitch_classes.add(PitchClass(pitch_class_nr)) self.chroma[pitch_class_nr] += (self._note_duration_ratio_in_event(note) * note.velocity)
def from_pitch_name(cls, pitch_name): """ Create a Pitch instance based on its pitch name (a str consisting of pitch class and octave number (e.g. 'A4') Note: works only with single sharp/flats; fails with double sharps/flats :param pitch_name: Name, consisting of pitch class and octave number :return: Pitch, as specified by its pitch name >>> Pitch.from_pitch_name('A4').midi_pitch 69 >>> Pitch.from_pitch_name('Bb4').midi_pitch # We can handle single sharps or flats 70 >>> Pitch.from_pitch_name('Bbb4') # We cannot handle double sharps or flats Traceback (most recent call last): ... ValueError: invalid literal for int() with base 10: 'b4' """ if 'b' in pitch_name or '#' in pitch_name: return cls.from_pitch_class_and_octave_number( PitchClass.from_harte_pitch_class(pitch_name[0:2]), int(pitch_name[2:])) return cls.from_pitch_class_and_octave_number( PitchClass.from_harte_pitch_class(pitch_name[0]), int(pitch_name[1:]))
def find_most_likely_chord(self, chord_vocabulary: ChordVocabulary) -> Tuple[ChordAnnotationItem, float]: """ Find the chord to which the chroma of this event matches best. :param chord_vocabulary: List of all chords for classification """ best_matching_chord_score = -2.99 best_matching_chord_str = 'X' best_key_note_weight = 0 for chord_template in chord_vocabulary.chord_templates: chord_score = self._score_compared_to_template(chord_template) if chord_score > best_matching_chord_score or (chord_score == best_matching_chord_score and self.chroma[chord_template.key] > best_key_note_weight): best_matching_chord_score = chord_score best_matching_chord_str = str(PitchClass(chord_template.key)) + chord_template.mode best_key_note_weight = self.chroma[chord_template.key] if best_matching_chord_str == 'X': most_likely_chord = None else: most_likely_chord = Chord.from_common_tab_notation_string(best_matching_chord_str) return ChordAnnotationItem(self.start_time, self.end_time, most_likely_chord), best_matching_chord_score
def __init__(self, chord_vocabulary: ChordVocabulary): self.alphabet_list = ['N'] + [ str(PitchClass(chord_template.key)) + chord_template.mode for chord_template in chord_vocabulary.chord_templates ] self.chord_vocabulary_name = chord_vocabulary.name