def str_msg_to_dict(str_message): message = mido.parse_string(str_message) dict_message = {'type': message.type} for name in MIDI_MESSAGES.get(message.type, ()): dict_message[name] = getattr(message, name) return dict_message
def str_msg_to_dict(str_message): message = mido.parse_string(str_message) dict_msg = {'type': message.type} for name in message._spec.arguments: dict_msg[name] = getattr(message, name) return dict_msg
def readin_strings(infile, comment=None): """ Read in string-encoded messages separated by line from a text mode file object. Similar to mido.parse_string_stream, except doesn't deal with the exceptions. Can also ignore simple comments, delimited by the comment parameter (uses str.partition internally). Generator, yields messages lazily. """ if comment is not None: for line in infile: msgl, _, _ = line.partition(comment) msg = msgl.strip() if msg: yield mido.parse_string(msg) else: for line in infile: yield mido.parse_string(line)
def __iter__(self): for line in sys.stdin: line = line.split('#', 1)[0].strip() if line.startswith('<message ') and line.endswith('>'): yield parse_string(line[9:-1]) elif line.startswith('<meta message ') and line.endswith('>'): yield parse_meta_message(line[14:-1]) elif line == '': pass else: raise ValueError("unrecognized line: " + repr(line))
def create_tiles(filepath): global tile_limit # check to see if tile limit has been set if tile_limit is not None: single_tile_limit = tile_limit # store file as MidiFile obj mid = MidiFile(filepath) msglist = [] # append all messages beyond header track to list for track in mid.tracks[1:]: for msg in track: msglist.append(msg) # stores accumulated time of each message index acc_time_index = {} acc_time = 0 for i in range(0, len(msglist)): time = int(re.search(r'time=(\d+)', str(msglist[i])).group(1)) acc_time += time acc_time_index.update({i: acc_time}) # check each offset of the message list for offset in range(0, len(msglist)): # check across a range of wavelengths for wavelength in range(lower_wavelength, upper_wavelength): # validate if the tile repeats isvalid = True for i in range(wavelength): if tile_limit is not None: if single_tile_limit == 0: break # ensure values do not exceed list index range if offset+i >= len(msglist) or offset+wavelength+i >= len(msglist): isvalid = False break if (msglist[offset+i] != msglist[offset+wavelength+i]): isvalid = False break # create tile from matching wavelength if (isvalid): tile = [] for i in range(wavelength): tile.append(msglist[offset+i]) # dummy MidiFile created to determine absolute time of tile for metadata temp_mid = mido.MidiFile(type=1) temp_mid.ticks_per_beat = mid.ticks_per_beat track = mido.MidiTrack() if find_program_change(filepath) is not None: prog_change = mido.parse_string(re.sub( r'time=(\d+)', r'time=0', str(find_program_change(filepath)))) track.append(prog_change) for line in tile: track.append(line) temp_mid.tracks.append(track) # save tile metadata to json formatted string tick_time = acc_time_index[offset] current_time = mido.tick2second( tick_time, mid.ticks_per_beat, find_tempo(filepath)) tile_dict = { 'file': filepath, 'offset': offset, 'wavelength': wavelength, 'start_time_seconds': ('%.2f' % current_time), 'total_length_seconds': ('%.2f' % temp_mid.length) } meta_dict = json.dumps(tile_dict) # MidiFile to be created as tile new_mid = mido.MidiFile(type=1) new_mid.ticks_per_beat = mid.ticks_per_beat # header track containing same info as original file header_track = mido.MidiTrack() for msg in mid.tracks[0]: if msg.type != 'end_of_track': header_track.append(msg) header_track.append(MetaMessage( 'text', text=str(tile_dict), time=0)) new_mid.tracks.append(header_track) # music track containing the notes music_track = mido.MidiTrack() # add program change message if find_program_change(filepath) is not None: prog_change = mido.parse_string(re.sub( r'time=(\d+)', r'time=0', str(find_program_change(filepath)))) music_track.append(prog_change) # add notes from tile list for line in tile: music_track.append(line) new_mid.tracks.append(music_track) try: # save to new file if tile within valid time range if new_mid.length > 0 and new_mid.length <= maximum_time: file_name = os.path.basename(filepath) instrument = int( re.search(r'program=(\d+)', str(find_program_change(filepath))).group(1)) tile_dir = '/tiles/' + \ instruments[instrument]+'/' Path( output_dir+tile_dir).mkdir(parents=True, exist_ok=True) new_mid.save(output_dir+tile_dir+'%s_%s_%d_%d.mid' % (file_name[:-4], instruments[instrument], offset, wavelength)) # print info to screen for development """ print('\nFile name: %s' % filepath) for i, track in enumerate(new_mid.tracks): print('Track number: %d' % (i+1)) for msg in track: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n') """ try: # save json file json_dir = '/tile_metadata/' + \ instruments[instrument]+'/' Path( output_dir+json_dir).mkdir(parents=True, exist_ok=True) f = open(output_dir+json_dir+'%s_%s_%d_%d.json' % ( file_name[:-4], instruments[instrument], offset, wavelength), 'w') f.write(meta_dict) except: print('JSON object not created for file: %s\n' % filepath) for msg in new_mid.tracks[0]: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n____________________________________________________________________________________________________________________________________\n\n') # dacrement tile limit for next loop if tile_limit is not None: single_tile_limit -= 1 except: print('Error with tile creation for file: %s\nAttempted tile length %d' % ( filepath, new_mid.length)) for msg in new_mid.tracks[0]: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n____________________________________________________________________________________________________________________________________\n\n')
def create_words(filepath): global word_limit # check to see if word limit has been set if word_limit is not None: single_word_limit = word_limit # store file as MidiFile obj mid = MidiFile(filepath) note_on_list = [] # new list of relevant messages for track in mid.tracks: if has_note_on(track): for msg in track: if msg.type == 'note_on' or msg.type == 'note_off': note_on_list.append(str(msg)) # to store accumulated time of message index acc_time = 0 acc_time_seconds = 0 # dictionary with each possible note stored, # to see what notes are on/off at any given offset note_dict = {} for i in range(0, 128): note_dict.update({i: {'on': False}}) # temporary list to store annotated messages note_on_new = [] for i in range(0, len(note_on_list)): # capture note number, velocity & time, note number in dictionary note = int(re.search(r'note=(\d+)', note_on_list[i]).group(1)) velocity = int(re.search(r'velocity=(\d+)', note_on_list[i]).group(1)) time = int(re.search(r'time=(\d+)', note_on_list[i]).group(1)) note_info = note_dict[note] try: # find time delta of next message in index next_note_time = int( re.search(r'time=(\d+)', note_on_list[i + 1]).group(1)) except: pass if i > 0: # find time delta of previous message in index previous_note = int( re.search(r'note=(\d+)', note_on_list[i - 1]).group(1)) else: previous_note = note # increment accumulated time at offset, find time of note at offset and next offset acc_time += time note_time_seconds = mido.tick2second(time, mid.ticks_per_beat, find_tempo(filepath)) next_note_time_seconds = mido.tick2second(next_note_time, mid.ticks_per_beat, find_tempo(filepath)) acc_time_seconds = mido.tick2second(acc_time, mid.ticks_per_beat, find_tempo(filepath)) # update dictionary with note status # if on, write to note_on_new with '-1' if 'note_on' in note_on_list[i] and velocity > 0: note_info['on'] = True note_on_new.append(note_on_list[i] + ' - 1') # if off, find out if there will be silence elif 'note_off' in note_on_list[i] or velocity == 0: note_info['on'] = False notes_on = [] # find out how many notes are on at this offset for j in note_dict: if note_dict[j]['on'] == True: notes_on.append(note_dict[j]) if len(notes_on) == 0: # find length of silence if all notes off silence_start = acc_time_seconds - note_time_seconds next_note_start = acc_time_seconds + next_note_time_seconds silence_length = next_note_start - silence_start # append '-0' to new list if silence above minimum threshold if silence_length > minimum_silence: note_on_new.append(note_on_list[i] + ' - 0') else: note_on_new.append(note_on_list[i] + ' - 1') else: note_on_new.append(note_on_list[i] + ' - 1') # store accumulated time of each message index acc_time_index = {} acc_time = 0 for i in range(0, len(note_on_list)): time = int(re.search(r'time=(\d+)', str(note_on_list[i])).group(1)) acc_time += time acc_time_index.update({i: acc_time}) for i in range(0, len(note_on_new)): # check if word limit is exceeded here if word_limit is not None and single_word_limit == 0: break previous_msg = note_on_new[i - 1] if int(previous_msg[-1]) == 0: # note ended with silence silence_time = int( re.search(r'time=(\d+)', note_on_new[i]).group(1)) silence_time_sec = mido.tick2second(silence_time, mid.ticks_per_beat, find_tempo(filepath)) if silence_time_sec > minimum_silence: # create word if silence above threshold word = [] # set first note_on time attribute to 0 to trim any start silence count = 0 for j in range(i, len(note_on_new)): if count == 0: word_lines = re.sub(r'time=(\d+)', r'time=0', note_on_new[j]) count += 1 else: word_lines = note_on_new[j] # remaining messages appended with '-1' or '-0' removed if int(word_lines[-1]) == 1: word.append(word_lines[:-4]) if int(word_lines[-1]) == 0: word.append(word_lines[:-4]) break # dummy mid created to determine absolute time of word for metadata temp_mid = mido.MidiFile(type=1) temp_mid.ticks_per_beat = mid.ticks_per_beat track = mido.MidiTrack() if find_program_change(filepath) is not None: prog_change = mido.parse_string( re.sub(r'time=(\d+)', r'time=0', str(find_program_change(file)))) track.append(prog_change) for line in word: track.append(mido.parse_string(line)) temp_mid.tracks.append(track) # save word metadata to json formatted string tick_time = acc_time_index[i] current_time = mido.tick2second(tick_time, mid.ticks_per_beat, find_tempo(filepath)) word_dict = { 'file': filepath, 'offset': i, 'wavelength': len(word), 'start_time_seconds': ('%.2f' % current_time), 'total_length_seconds': ('%.2f' % temp_mid.length) } meta_dict = json.dumps(word_dict) # MidiFile to be created as tile new_mid = mido.MidiFile(type=1) new_mid.ticks_per_beat = mid.ticks_per_beat # header track containing same info as original file header_track = mido.MidiTrack() for msg in mid.tracks[0]: header_track.append(msg) header_track.append( MetaMessage('text', text=str(word_dict), time=0)) new_mid.tracks.append(header_track) # music track containing the notes music_track = mido.MidiTrack() # add program change message if find_program_change(filepath) is not None: prog_change = mido.parse_string( re.sub(r'time=(\d+)', r'time=0', str(find_program_change(filepath)))) music_track.append(prog_change) # add notes from word list for line in word: music_track.append(mido.parse_string(line)) new_mid.tracks.append(music_track) try: # save to new file if word within valid time range if new_mid.length > 0 and new_mid.length <= maximum_time: file_name = os.path.basename(filepath) instrument = int( re.search( r'program=(\d+)', str(find_program_change(filepath))).group(1)) word_dir = '/words/' + instruments[instrument] + '/' Path(output_dir + word_dir).mkdir(parents=True, exist_ok=True) new_mid.save(output_dir + word_dir + '%s_%s_%d_%d.mid' % (file_name[:-4], instruments[instrument], i, len(word))) # print info to screen for development """ print('\nFile name: %s' % filepath) for i, track in enumerate(new_mid.tracks): print('Track number: %d' % (i+1)) for msg in track: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n') """ try: # save json file json_dir = '/word_metadata/' + \ instruments[instrument]+'/' Path(output_dir + json_dir).mkdir(parents=True, exist_ok=True) f = open( output_dir + json_dir + '%s_%s_%d_%d.json' % (file_name[:-4], instruments[instrument], i, len(word)), 'w') f.write(meta_dict) except: print('JSON object not created for file: %s\n' % filepath) for msg in new_mid.tracks[0]: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n____________________________________________________________________________________________________________________________________\n\n' ) # dacrement word limit for next loop if word_limit is not None: single_word_limit -= 1 except: print( 'Error with word creation for file: %s\nAttempted tile length %d' % (filepath, new_mid.length)) for msg in new_mid.tracks[0]: print(msg) print( '\n____________________________________________________________________________________________________________________________________\n____________________________________________________________________________________________________________________________________\n\n' )
def send_from_str(self, str_message): self.send(mido.parse_string(str_message))
def clean(filepath): # store file as MidiFile obj, create new obj to store output mid = MidiFile(filepath) ticksperbeat = mid.ticks_per_beat newmid = MidiFile(type=1) newmid.ticks_per_beat = ticksperbeat # in the event that an instrument-isolated file contains multiple tracks of the same instrument, # this will ensure that when the start time is trimmed, the timing is all kept correct start_times = [] for track in mid.tracks: for msg in track: # finds first note_on message time, i.e. starting time # for all tracks in file if msg.type == 'note_on': start_time = msg.time start_times.append(start_time) break start_times.sort() if start_times: first_start_time = start_times[0] # if no start time, file has no note on messages and is invalid else: pass # iterate through tracks in file for i, track in enumerate(mid.tracks): # new track for cleaned output newtrack = mido.MidiTrack() # new list to store messages ready for processing msglist = [msg for msg in track] # each processed file will have one of each: tempo, time signature, # key signature and program change messages count = 0 tempocount = 0 timesigcount = 0 keysigcount = 0 progchangecount = 0 # list of accepted messages to help filter any unwanted ones goodmessages = [ 'note_on', 'note_off', 'program_change', 'set_tempo', 'time_signature', 'key_signature' ] # iterate through each message and determine if it will be kept or discarded for msg in msglist: if msg.type == 'program_change': # finds first program change message to store instrument number instrument = msg.program progchangecount += 1 # ensures no unwanted messages are appended if str(msg.type) not in goodmessages: continue if msg.type == 'set_tempo': # appends one tempo message at time delta of 0 to new track if tempocount < 1 and msg.time == 0: newtrack.append( MetaMessage('set_tempo', tempo=msg.tempo, time=msg.time)) tempocount += 1 else: continue if msg.type == 'time_signature': # appends one time signature message at time delta of 0 to new track if timesigcount < 1 and msg.time == 0: newtrack.append( MetaMessage('time_signature', numerator=msg.numerator, denominator=msg.denominator, time=msg.time)) timesigcount += 1 else: continue if msg.type == 'key_signature': # appends one key signature message at time delta of 0 to new track if keysigcount < 1 and msg.time == 0: newtrack.append( MetaMessage('key_signature', key=msg.key, time=msg.time)) keysigcount += 1 else: continue # ensures first note on message has time delta of zero, # adjusts other potential tracks to equivalent time difference if count < 1: if msg.type == 'note_on': start_time = msg.time if start_time == first_start_time: trimsilence = re.sub(r'time=(\d+)', r'time=0', str(msg)) else: time_diff = 'time=%d' % (start_time - first_start_time) trimsilence = re.sub(r'time=(\d+)', time_diff, str(msg)) newtrack.append(mido.parse_string(trimsilence)) count += 1 continue # append all other messages to new track if not msg.type == 'set_tempo' and not msg.type == 'time_signature' and not msg.type == 'key_signature': newtrack.append(msg) # append header track to new MidiFile obj if i == 0: newmid.tracks.append(newtrack) continue # append all other valid tracks to new MidiFile obj if progchangecount > 0 and i > 0: newmid.tracks.append(newtrack) # create new directory to store output new_dir = output_dir + '/cleaned/' + instruments[instrument] + '/' Path(new_dir).mkdir(parents=True, exist_ok=True) file_name = os.path.basename(filepath) # validates and saves new file if is_valid(newmid): newmid.save(new_dir + file_name) else: print('Invalid file not saved: %s' % filepath)