Source code for scamp_extensions.parsing.midi

"""
Utilities for scraping notes from MIDI files.
"""

#  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  #
#  This file is part of SCAMP (Suite for Computer-Assisted Music in Python)                      #
#  Copyright © 2020 Marc Evanstein <marc@marcevanstein.com>.                                     #
#                                                                                                #
#  This program is free software: you can redistribute it and/or modify it under the terms of    #
#  the GNU General Public License as published by the Free Software Foundation, either version   #
#  3 of the License, or (at your option) any later version.                                      #
#                                                                                                #
#  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;     #
#  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.     #
#  See the GNU General Public License for more details.                                          #
#                                                                                                #
#  You should have received a copy of the GNU General Public License along with this program.    #
#  If not, see <http://www.gnu.org/licenses/>.                                                   #
#  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++  #

from typing import List
from mido import MidiFile
from collections import namedtuple

Note = namedtuple("Note", "track channel pitch volume start_time length")


[docs]def scrape_midi_file_to_note_list(midi_file_path) -> List[Note]: """ Scrapes a list of :class:`Note` objects from all of the tracks of the given MIDI file. :param midi_file_path: path to midi file """ mid = MidiFile(midi_file_path, clip=True) notes_started = {} notes = [] for which_track, track in enumerate(mid.tracks): t = 0 for message in track: t += message.time / mid.ticks_per_beat if message.type == "note_off" or (message.type == "note_on" and message.velocity == 0): try: volume, start_time = notes_started[(message.note, message.channel)] notes.append(Note(which_track, message.channel, message.note, volume, start_time, t - start_time)) except KeyError: print("KEY ERROR") pass elif message.type == "note_on": notes_started[(message.note, message.channel)] = message.velocity / 127, t notes.sort(key=lambda note: note.start_time) return notes
[docs]def scrape_midi_file_to_dict(midi_file_path) -> dict: """ Scrapes a dictionary of note info from a MIDI file. :param midi_file_path: the MIDI file path :return: a dict with the following keys, each of which is presented in chronological order of the notes from which they derive: "pitches", "start_times", "volumes", "lengths", "inter_onset_times" (how long since the last note started), "tracks" """ notes = scrape_midi_file_to_note_list(midi_file_path) tracks, channels, pitches, volumes, start_times, lengths = zip(*notes) tracks = list(tracks) pitches = list(pitches) start_times = list(start_times) volumes = list(volumes) lengths = list(lengths) inter_onset_times = [t2 - t1 for t1, t2 in zip(start_times[:-1], start_times[1:])] return { "pitches": pitches, "start_times": start_times, "volumes": volumes, "lengths": lengths, "inter_onset_times": inter_onset_times, "tracks": tracks }