Spaces:
Build error
Build error
"Transform functions for raw midi files" | |
from enum import Enum | |
import music21 | |
PIANO_TYPES = list(range(24)) + list(range(80, 96)) # Piano, Synths | |
PLUCK_TYPES = list(range(24, 40)) + list(range(104, 112)) # Guitar, Bass, Ethnic | |
BRIGHT_TYPES = list(range(40, 56)) + list(range(56, 80)) | |
PIANO_RANGE = (21, 109) # https://en.wikipedia.org/wiki/Scientific_pitch_notation | |
class Track(Enum): | |
PIANO = 0 # discrete instruments - keyboard, woodwinds | |
PLUCK = 1 # continuous instruments with pitch bend: violin, trombone, synths | |
BRIGHT = 2 | |
PERC = 3 | |
UNDEF = 4 | |
type2inst = { | |
# use print_music21_instruments() to see supported types | |
Track.PIANO: 0, # Piano | |
Track.PLUCK: 24, # Guitar | |
Track.BRIGHT: 40, # Violin | |
Track.PERC: 114, # Steel Drum | |
} | |
# INFO_TYPES = set(['TIME_SIGNATURE', 'KEY_SIGNATURE']) | |
INFO_TYPES = set(['TIME_SIGNATURE', 'KEY_SIGNATURE', 'SET_TEMPO']) | |
def file2mf(fp): | |
mf = music21.midi.MidiFile() | |
if isinstance(fp, bytes): | |
mf.readstr(fp) | |
else: | |
mf.open(fp) | |
mf.read() | |
mf.close() | |
return mf | |
def mf2stream(mf): return music21.midi.translate.midiFileToStream(mf) | |
def is_empty_midi(fp): | |
if fp is None: return False | |
mf = file2mf(fp) | |
return not any([t.hasNotes() for t in mf.tracks]) | |
def num_piano_tracks(fp): | |
music_file = file2mf(fp) | |
note_tracks = [t for t in music_file.tracks if t.hasNotes() and get_track_type(t) == Track.PIANO] | |
return len(note_tracks) | |
def is_channel(t, c_val): | |
return any([c == c_val for c in t.getChannels()]) | |
def track_sort(t): # sort by 1. variation of pitch, 2. number of notes | |
return len(unique_track_notes(t)), len(t.events) | |
def is_piano_note(pitch): | |
return (pitch >= PIANO_RANGE[0]) and (pitch < PIANO_RANGE[1]) | |
def unique_track_notes(t): | |
return { e.pitch for e in t.events if e.pitch is not None } | |
def compress_midi_file(fp, cutoff=6, min_variation=3, supported_types=set([Track.PIANO, Track.PLUCK, Track.BRIGHT])): | |
music_file = file2mf(fp) | |
info_tracks = [t for t in music_file.tracks if not t.hasNotes()] | |
note_tracks = [t for t in music_file.tracks if t.hasNotes()] | |
if len(note_tracks) > cutoff: | |
note_tracks = sorted(note_tracks, key=track_sort, reverse=True) | |
supported_tracks = [] | |
for idx,t in enumerate(note_tracks): | |
if len(supported_tracks) >= cutoff: break | |
track_type = get_track_type(t) | |
if track_type not in supported_types: continue | |
pitch_set = unique_track_notes(t) | |
if (len(pitch_set) < min_variation): continue # must have more than x unique notes | |
if not all(map(is_piano_note, pitch_set)): continue # must not contain midi notes outside of piano range | |
# if track_type == Track.UNDEF: print('Could not designate track:', fp, t) | |
change_track_instrument(t, type2inst[track_type]) | |
supported_tracks.append(t) | |
if not supported_tracks: return None | |
music_file.tracks = info_tracks + supported_tracks | |
return music_file | |
def get_track_type(t): | |
if is_channel(t, 10): return Track.PERC | |
i = get_track_instrument(t) | |
if i in PIANO_TYPES: return Track.PIANO | |
if i in PLUCK_TYPES: return Track.PLUCK | |
if i in BRIGHT_TYPES: return Track.BRIGHT | |
return Track.UNDEF | |
def get_track_instrument(t): | |
for idx,e in enumerate(t.events): | |
if e.type == 'PROGRAM_CHANGE': return e.data | |
return None | |
def change_track_instrument(t, value): | |
for idx,e in enumerate(t.events): | |
if e.type == 'PROGRAM_CHANGE': e.data = value | |
def print_music21_instruments(): | |
for i in range(200): | |
try: print(i, music21.instrument.instrumentFromMidiProgram(i)) | |
except: pass |