import mido import argparse import sys import os def remove_redundant_midi(input_path, verbose=False): """ Removes redundant MIDI data from the input MIDI file and saves the cleaned file with '_redundancy_check' appended to the original filename. Args: input_path (str): Path to the input MIDI file. verbose (bool): If True, prints detailed processing information. """ try: midi = mido.MidiFile(input_path) if verbose: print(f"Loaded MIDI file '{input_path}' successfully.") except IOError: print(f"Error: Cannot open input MIDI file '{input_path}'. Please check the path.") sys.exit(1) except mido.KeySignatureError as e: print(f"Error reading MIDI file: {e}") sys.exit(1) cleaned_midi = mido.MidiFile() cleaned_midi.ticks_per_beat = midi.ticks_per_beat total_messages = 0 removed_messages = 0 for i, track in enumerate(midi.tracks): cleaned_track = mido.MidiTrack() last_state = {} # To keep track of the last control change or similar messages last_msg = None track_removed = 0 track_total = 0 for msg in track: track_total += 1 total_messages += 1 # Remove consecutive duplicate messages if last_msg is not None and msg == last_msg: if verbose: print(f"Track {i}: Removed duplicate message {msg}") removed_messages += 1 track_removed += 1 continue # Remove redundant control change messages if msg.type in ['control_change', 'program_change', 'pitchwheel', 'aftertouch', 'channel_pressure']: channel = msg.channel key = f"{msg.type}_{channel}" if key in last_state and last_state[key] == msg: if verbose: print(f"Track {i}: Removed redundant state message {msg}") removed_messages += 1 track_removed += 1 continue # Redundant state message last_state[key] = msg cleaned_track.append(msg) # Remove redundant note_off messages if note_on with velocity 0 is used elif msg.type == 'note_off': # Depending on MIDI implementation, decide if it's redundant # For this script, we'll assume they are necessary and keep them cleaned_track.append(msg) elif msg.type == 'note_on' and msg.velocity == 0: # This is equivalent to note_off; decide whether to keep or remove note_off messages # For simplicity, keep both cleaned_track.append(msg) else: # For all other message types, simply append if not duplicate cleaned_track.append(msg) last_msg = msg cleaned_midi.tracks.append(cleaned_track) if verbose: print(f"Track {i}: Processed {track_total} messages, removed {track_removed} redundant messages.") # Generate the output file name by appending '_redundancy_check' before the file extension base, ext = os.path.splitext(input_path) output_path = f"{base}_redundancy_check{ext}" try: cleaned_midi.save(output_path) if verbose: print(f"Saved cleaned MIDI to '{output_path}'.") print(f"Total messages processed: {total_messages}") print(f"Total messages removed: {removed_messages}") else: print(f"Successfully saved cleaned MIDI to '{output_path}'.") except IOError: print(f"Error: Cannot write to output file '{output_path}'. Please check the path and permissions.") sys.exit(1) def parse_arguments(): """ Parses command-line arguments. Returns: argparse.Namespace: The parsed arguments. """ parser = argparse.ArgumentParser(description="Remove redundant MIDI data from a MIDI file and save the cleaned file with '_redundancy_check' appended to the filename.") parser.add_argument('input_midi', type=str, help='Path to the input MIDI file.') parser.add_argument('-v', '--verbose', action='store_true', help='Enable verbose output.') return parser.parse_args() def main(): args = parse_arguments() # Check if input file exists if not os.path.isfile(args.input_midi): print(f"Error: The input file '{args.input_midi}' does not exist.") sys.exit(1) remove_redundant_midi(args.input_midi, args.verbose) if __name__ == "__main__": main()