caslabs's picture
Upload 37 files
f35cc94
"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