Esempio n. 1
0
def test_key_name_to_key_number():
    # First, test that number->name->number works
    for key_number in range(24):
        assert pretty_midi.key_name_to_key_number(
            pretty_midi.key_number_to_key_name(key_number)) == key_number
    # Explicitly test all valid input
    key_pc = {'c': 0, 'd': 2, 'e': 4, 'f': 5, 'g': 7, 'a': 9, 'b': 11}
    for key in key_pc:
        for flatsharp, shift in zip(['', '#', 'b'], [0, 1, -1]):
            key_number = (key_pc[key] + shift) % 12
            for space in [' ', '']:
                for mode in ['M', 'Maj', 'Major', 'maj', 'major']:
                    assert pretty_midi.key_name_to_key_number(
                        key + flatsharp + space + mode) == key_number
                # Also ensure uppercase key name plus no mode string
                assert pretty_midi.key_name_to_key_number(
                    key.upper() + flatsharp + space) == key_number
                for mode in ['m', 'Min', 'Minor', 'min', 'minor']:
                    assert pretty_midi.key_name_to_key_number(
                        key + flatsharp + space + mode) == key_number + 12
                assert pretty_midi.key_name_to_key_number(
                    key + flatsharp + space) == key_number + 12
    # Test some invalid inputs
    for invalid_key in ['C#  m', 'C# ma', 'ba', 'bm m', 'f## Major', 'O']:
        with pytest.raises(ValueError):
            pretty_midi.key_name_to_key_number(invalid_key)
def test_key_name_to_key_number():
    # First, test that number->name->number works
    for key_number in range(24):
        assert pretty_midi.key_name_to_key_number(
            pretty_midi.key_number_to_key_name(key_number)) == key_number
    # Explicitly test all valid input
    key_pc = {'c': 0, 'd': 2, 'e': 4, 'f': 5, 'g': 7, 'a': 9, 'b': 11}
    for key in key_pc:
        for flatsharp, shift in zip(['', '#', 'b'], [0, 1, -1]):
            key_number = (key_pc[key] + shift) % 12
            for space in [' ', '']:
                for mode in ['M', 'Maj', 'Major', 'maj', 'major']:
                    assert pretty_midi.key_name_to_key_number(
                        key + flatsharp + space + mode) == key_number
                # Also ensure uppercase key name plus no mode string
                assert pretty_midi.key_name_to_key_number(key.upper() +
                                                          flatsharp +
                                                          space) == key_number
                for mode in ['m', 'Min', 'Minor', 'min', 'minor']:
                    assert pretty_midi.key_name_to_key_number(
                        key + flatsharp + space + mode) == key_number + 12
                assert pretty_midi.key_name_to_key_number(
                    key + flatsharp + space) == key_number + 12
    # Test some invalid inputs
    for invalid_key in ['C#  m', 'C# ma', 'ba', 'bm m', 'f## Major', 'O']:
        with pytest.raises(ValueError):
            pretty_midi.key_name_to_key_number(invalid_key)
Esempio n. 3
0
File: utils.py Progetto: fuurin/midi
def estimate_key_signature(pm, by_name=True, fs=100, vel_thresh=0):
    if len(pm.key_signature_changes) > 1:
        raise ValueError(
            "estimate_key function is for single key signature midi data.")

    # 相対chromaを計算
    rel_chroma = relative_chroma(pm, fs=fs, vel_thresh=vel_thresh)
    if rel_chroma is None:
        return None, np.array([0] * 12)

    # 最もスケールに合致したシフト数を計算
    max_score, max_shift = 0.0, 0
    major_scale = [1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1]
    for s in range(12):
        shift_scale = np.roll(major_scale, s).astype(bool)
        score = sum(rel_chroma[shift_scale])
        if score > max_score:
            max_score, max_shift = score, s

    # 1度が多ければMajor,6度が多ければminorとする
    if rel_chroma[max_shift] < rel_chroma[max_shift - 3]:
        key_number = (max_shift + -3) % 12 + 12
    else:
        key_number = max_shift

    return pretty_midi.key_number_to_key_name(
        key_number) if by_name else key_number, rel_chroma
Esempio n. 4
0
def get_key(midi_file):
    ''' Load in key labels from a MIDI file '''
    # Load in MIDI object and grab key change events

    try:
        pm = pretty_midi.PrettyMIDI(midi_file)
        key_changes = pm.key_signature_changes
        # Convert each key change's number to a string (like 'C Major')
        # Also convert it to lowercase, for mir_eval's sake
        key_changes = [
            pretty_midi.key_number_to_key_name(k.key_number).lower()
            for k in key_changes
        ]
    except:
        Warning('error with prettyMIDI')
        return None

    if key_changes:
        key = key_changes[0]
        tonic, mode = key.split(' ')
        key = tonic.capitalize() + ' ' + mode
        if key == 'C major':
            return None
        else:
            return key
    else:
        return None
Esempio n. 5
0
def create_chords(chord_list):
    nums = []
    all_keys = np.zeros((12, len(chord_list)))
    for i, chord in enumerate(chord_list):
        chord_root = chord.split()[0][0]
        nums.append(chord_root)
        num = pretty_midi.key_name_to_key_number(chord_root)
        all_keys[0, i] = num

        for i in range(1, 12):
            all_keys[i] = (all_keys[i-1] + 1) % 12
    all_transposes = [] 
    for i in range(all_keys.shape[0]):
        progression = []
        for j in range(all_keys.shape[1]):
            root = pretty_midi.key_number_to_key_name(int(all_keys[i, j]))
            if root[1] == 'b':
                take = root[:2]
            else:
                take = root[0]
            chord_type = chord_list[j][1:]
            progression.append(f'{take}{chord_type}')
        all_transposes.append(progression)
    return all_transposes
Esempio n. 6
0
def extract_ground_truth(diagnostics_group):
    """
    Extract ground-truth information from one or more MIDI files about a single
    MIDI file based on the results in one or more diagnostics files and return
    a JAMS object with all of the annotations compiled.

    Parameters
    ----------
    - diagnostics_group : list of dict
        List of dicts of diagnostics, each about a successful alignment of a
        different MIDI file to a single audio file.
    """
    # Construct the JAMS object
    jam = jams.JAMS()
    # Load in the first diagnostics (doesn't matter which as they all
    # should correspond the same audio file)
    diagnostics = diagnostics_group[0]
    # Load in the audio file to get its duration for the JAMS file
    audio, fs = librosa.load(diagnostics['audio_filename'],
                             feature_extraction.AUDIO_FS)
    jam.file_metadata.duration = librosa.get_duration(y=audio, sr=fs)
    # Also store metadata about the audio file, retrieved from the MSD
    jam.file_metadata.identifiers = {'track_id': diagnostics['audio_id']}
    jam.file_metadata.artist = MSD_LIST[diagnostics['audio_id']]['artist']
    jam.file_metadata.title = MSD_LIST[diagnostics['audio_id']]['title']

    # Iterate over the diagnostics files supplied
    for diagnostics in diagnostics_group:

        # Create annotation metadata object, shared across annotations
        commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
        commit_url = "http://github.com/craffel/midi-dataset/tree/" + commit
        annotator = {
            'midi_md5': diagnostics['midi_md5'],
            'commit_url': commit_url,
            'confidence': diagnostics['score']
        }
        annotation_metadata = jams.AnnotationMetadata(
            curator=jams.Curator('Colin Raffel', '*****@*****.**'),
            version='0.0.1b',
            corpus='Million Song Dataset MIDI Matches',
            annotator=annotator,
            annotation_tools=(
                'MIDI files were matched and aligned to audio files using the '
                'code at http://github.com/craffel/midi-dataset.  Information '
                'was extracted from MIDI files using pretty_midi '
                'https://github.com/craffel/pretty-midi.'),
            annotation_rules=(
                'Beat locations and key change times were linearly '
                'interpolated according to an audio-to-MIDI alignment.'),
            validation=(
                'Only MIDI files with alignment confidence scores >= .5 were '
                'considered "correct".  The confidence score can be used as a '
                'rough guide to the potential correctness of the annotation.'),
            data_source='Inferred from a MIDI file.')

        # Load the extracted features
        midi_features = deepdish.io.load(diagnostics['midi_features_filename'])
        audio_features = deepdish.io.load(
            diagnostics['audio_features_filename'])
        # Load in the original MIDI file
        midi_object = pretty_midi.PrettyMIDI(diagnostics['midi_filename'])
        # Compute the times of the frames (will be used for interpolation)
        midi_frame_times = feature_extraction.frame_times(
            midi_features['gram'])[diagnostics['aligned_midi_indices']]
        audio_frame_times = feature_extraction.frame_times(
            audio_features['gram'])[diagnostics['aligned_audio_indices']]

        # Get the interpolated beat locations and add them to the JAM
        adjusted_beats = interpolate_times(midi_object.get_beats(),
                                           midi_frame_times, audio_frame_times)
        # Create annotation record for the beats
        beat_a = jams.Annotation(namespace='beat')
        beat_a.annotation_metadata = annotation_metadata
        # Add beat timings to the annotation record
        for t in adjusted_beats:
            beat_a.append(time=t, duration=0.0)
        # Add beat annotation record to the JAMS file
        jam.annotations.append(beat_a)

        # Get key signature times and their string names
        key_change_times = [c.time for c in midi_object.key_signature_changes]
        key_names = [
            pretty_midi.key_number_to_key_name(c.key_number)
            for c in midi_object.key_signature_changes
        ]
        # JAMS requires that the key name be supplied in the form e.g.
        # "C:major" but pretty_midi returns things in the format "C Major",
        # so the following code converts to JAMS format
        key_names = [
            name.replace(' ', ':').replace('M', 'm') for name in key_names
        ]
        # Compute interpolated event times
        adjusted_key_change_times, adjusted_key_names = interpolate_times(
            key_change_times, midi_frame_times, audio_frame_times, key_names,
            True)
        # Create JAMS annotation for the key changes
        if len(adjusted_key_change_times) > 0:
            key_a = jams.Annotation(namespace='key_mode')
            key_a.annotation_metadata = annotation_metadata
            # We only have key start times from the MIDI file, but JAMS wants
            # durations too, so create a list of "end times"
            end_times = np.append(adjusted_key_change_times[1:],
                                  jam.file_metadata.duration)
            # Add key labels into the JAMS file
            for start, end, key in zip(adjusted_key_change_times, end_times,
                                       adjusted_key_names):
                key_a.append(time=start, duration=end - start, value=key)
            jam.annotations.append(key_a)

    return jam
Esempio n. 7
0
def extract_ground_truth(diagnostics_group):
    """
    Extract ground-truth information from one or more MIDI files about a single
    MIDI file based on the results in one or more diagnostics files and return
    a JAMS object with all of the annotations compiled.

    Parameters
    ----------
    - diagnostics_group : list of dict
        List of dicts of diagnostics, each about a successful alignment of a
        different MIDI file to a single audio file.
    """
    # Construct the JAMS object
    jam = jams.JAMS()
    # Load in the first diagnostics (doesn't matter which as they all
    # should correspond the same audio file)
    diagnostics = diagnostics_group[0]
    # Load in the audio file to get its duration for the JAMS file
    audio, fs = librosa.load(
        diagnostics['audio_filename'], feature_extraction.AUDIO_FS)
    jam.file_metadata.duration = librosa.get_duration(y=audio, sr=fs)
    # Also store metadata about the audio file, retrieved from the MSD
    jam.file_metadata.identifiers = {'track_id': diagnostics['audio_id']}
    jam.file_metadata.artist = MSD_LIST[diagnostics['audio_id']]['artist']
    jam.file_metadata.title = MSD_LIST[diagnostics['audio_id']]['title']

    # Iterate over the diagnostics files supplied
    for diagnostics in diagnostics_group:

        # Create annotation metadata object, shared across annotations
        commit = subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
        commit_url = "http://github.com/craffel/midi-dataset/tree/" + commit
        annotator = {'midi_md5': diagnostics['midi_md5'],
                     'commit_url': commit_url,
                     'confidence': diagnostics['score']}
        annotation_metadata = jams.AnnotationMetadata(
            curator=jams.Curator('Colin Raffel', '*****@*****.**'),
            version='0.0.1b', corpus='Million Song Dataset MIDI Matches',
            annotator=annotator,
            annotation_tools=(
                'MIDI files were matched and aligned to audio files using the '
                'code at http://github.com/craffel/midi-dataset.  Information '
                'was extracted from MIDI files using pretty_midi '
                'https://github.com/craffel/pretty-midi.'),
            annotation_rules=(
                'Beat locations and key change times were linearly '
                'interpolated according to an audio-to-MIDI alignment.'),
            validation=(
                'Only MIDI files with alignment confidence scores >= .5 were '
                'considered "correct".  The confidence score can be used as a '
                'rough guide to the potential correctness of the annotation.'),
            data_source='Inferred from a MIDI file.')

        # Load the extracted features
        midi_features = deepdish.io.load(diagnostics['midi_features_filename'])
        audio_features = deepdish.io.load(
            diagnostics['audio_features_filename'])
        # Load in the original MIDI file
        midi_object = pretty_midi.PrettyMIDI(diagnostics['midi_filename'])
        # Compute the times of the frames (will be used for interpolation)
        midi_frame_times = feature_extraction.frame_times(
            midi_features['gram'])[diagnostics['aligned_midi_indices']]
        audio_frame_times = feature_extraction.frame_times(
            audio_features['gram'])[diagnostics['aligned_audio_indices']]

        # Get the interpolated beat locations and add them to the JAM
        adjusted_beats = interpolate_times(
            midi_object.get_beats(), midi_frame_times, audio_frame_times)
        # Create annotation record for the beats
        beat_a = jams.Annotation(namespace='beat')
        beat_a.annotation_metadata = annotation_metadata
        # Add beat timings to the annotation record
        for t in adjusted_beats:
            beat_a.append(time=t, duration=0.0)
        # Add beat annotation record to the JAMS file
        jam.annotations.append(beat_a)

        # Get key signature times and their string names
        key_change_times = [c.time for c in midi_object.key_signature_changes]
        key_names = [pretty_midi.key_number_to_key_name(c.key_number)
                     for c in midi_object.key_signature_changes]
        # JAMS requires that the key name be supplied in the form e.g.
        # "C:major" but pretty_midi returns things in the format "C Major",
        # so the following code converts to JAMS format
        key_names = [name.replace(' ', ':').replace('M', 'm')
                     for name in key_names]
        # Compute interpolated event times
        adjusted_key_change_times, adjusted_key_names = interpolate_times(
            key_change_times, midi_frame_times, audio_frame_times, key_names,
            True)
        # Create JAMS annotation for the key changes
        if len(adjusted_key_change_times) > 0:
            key_a = jams.Annotation(namespace='key_mode')
            key_a.annotation_metadata = annotation_metadata
            # We only have key start times from the MIDI file, but JAMS wants
            # durations too, so create a list of "end times"
            end_times = np.append(adjusted_key_change_times[1:],
                                  jam.file_metadata.duration)
            # Add key labels into the JAMS file
            for start, end, key in zip(adjusted_key_change_times, end_times,
                                       adjusted_key_names):
                key_a.append(time=start, duration=end - start, value=key)
            jam.annotations.append(key_a)

    return jam
Esempio n. 8
0
def parse_key_number(key_number):
    """Parse key_number to string in pretty_midi.KeySignature.
	"""
    key = key_number_to_key_name(key_number)
    return generalize_key(key)
Esempio n. 9
0
            continue

        # pretty_midi also provides direct access to the pitch and start/end time of each note
        intervals = np.array(
            [[note.start, note.end]
             for note in pm.instruments[instrument_channel].notes])
        notes = np.array(
            [note.pitch for note in pm.instruments[instrument_channel].notes])

        # Get key data for each MIDI file.
        for key_change in pm.key_signature_changes:
            #            print('Key {} starting at time {:.2f}'.format(
            #                pretty_midi.key_number_to_key_name(key_change.key_number), key_change.time))
            #            print(pretty_midi.key_number_to_key_name(key_change.key_number))
            current_chord = create_chord_vec(
                pretty_midi.key_number_to_key_name(key_change.key_number))
            current_chord = [current_chord] * 8

        # Redefine each on note as a 1 rather than assigning a velcoity.
        tester = piano_roll >= 1
        for midi_note in range(piano_roll.shape[0]):
            for sixteenth_beat in range(piano_roll.shape[1]):
                if tester[midi_note][sixteenth_beat] == True:
                    piano_roll[midi_note][sixteenth_beat] = 1

        #If not divisible into exact number of bars, cut off the final part of bar.
        piano_roll_trimmed = piano_roll
        while piano_roll_trimmed.shape[1] / 16 % 1 != 0:  #while non-integer
            piano_roll_trimmed = np.delete(piano_roll_trimmed, -1, axis=1)

        #check to avoid zero error. Not sure why piano_roll_trimmed.shape[1]==0
Esempio n. 10
0
def midi_cleaner(data_folder,
                 allowed_time_sigs,
                 allowed_keys,
                 max_time_changes=1,
                 max_key_changes=1,
                 ignore_filters=False):
    '''
    ----------------------------------------------------------------------------
    A function to filter a group of MIDI files by selecting only the ones that
    meet the specified criteria supplied for key, time signature.

    The files are returned as a list of pretty_midi objects.

    Parameters:
    ############################################################################
    --data_folder--
    The path of the folder containing the files to be filtered

    --allowed_time_sigs--
    The time signatures to be allowed as an array of strings e.g. ['4/4']

    --allowed_keys--
    The key signatures to be allowed as an array of strings e.g. ['C Major', 'Bb Minor']

    --max_time_changes--
    The maximum number of time signature changes allowed. Default is 1.

    --max_key_changes--
    The maximum number of key signature changes allowed. Default is 1.

    --ignore_filters--
    If true, all MIDI files in the folder will be converted .

    ############################################################################

    Returns:
    A list of pretty_midi objects meeting the supplied filter settings
    ----------------------------------------------------------------------------
    '''

    midi_files = os.listdir(data_folder)

    errors = 0
    filtered_files = []
    size, count = len(midi_files), 0.0
    for num, midi_file in enumerate(midi_files):
        #print('Processing file {} of {}'.format(num+1,len(midi_files)))

        try:
            mid = pretty_midi.PrettyMIDI(os.path.join(data_folder, midi_file))

            if not ignore_filters:
                time_sig_is_good, key_is_good = False, False

                if mid.time_signature_changes and len(
                        mid.time_signature_changes) <= max_time_changes:
                    time_sig_is_good = all(
                        '{}/{}'.format(ts.numerator,
                                       ts.denominator) in allowed_time_sigs
                        for ts in mid.time_signature_changes)

                if mid.key_signature_changes and len(
                        mid.key_signature_changes) <= max_key_changes:
                    key_is_good = all(
                        pretty_midi.key_number_to_key_name(key.key_number) in
                        allowed_keys for key in mid.key_signature_changes)

                if time_sig_is_good and key_is_good:
                    filtered_files.append(mid)

            else:
                filtered_files.append(mid)
        except:
            errors += 1
        count += 1
        print('Processing files... {:4.2f}% complete.   \r'.format(
            100 * count / size),
              end='')
    print("")
    print('{} MIDI files found.'.format(len(filtered_files)))

    if errors:
        print('{} files could not be parsed.'.format(errors))

    return filtered_files