MIDI Tools - Tesla Coil MIDI Processing Suite
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

154 lines
5.7 KiB

import mido
import argparse
import sys
import os
def set_all_velocities(input_path, new_velocity, ignored_channels=None, verbose=False):
"""
Sets the velocity of all note_on and note_off messages in a MIDI file to a specified value,
ignoring specified channels, and saves the modified MIDI file with '_velfix' appended to the original filename.
Args:
input_path (str): Path to the input MIDI file.
new_velocity (int): The new velocity value (0-127) to set.
ignored_channels (set[int], optional): Set of MIDI channels to ignore (1-16). Defaults to None.
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)
total_messages = 0
modified_messages = 0
ignored_messages = 0
for track in midi.tracks:
for msg in track:
total_messages += 1
# Check if the message has a velocity attribute and a channel
if msg.type in ['note_on', 'note_off'] and msg.type == 'note_on' or msg.type == 'note_off':
# Some messages might not have a channel attribute
if hasattr(msg, 'channel'):
# MIDI channels are 0-15 in mido, so convert ignored_channels from 1-16 to 0-15
msg_channel = msg.channel + 1 # Convert to 1-based for comparison
if ignored_channels and msg_channel in ignored_channels:
ignored_messages += 1
continue # Skip modifying this message
# Modify velocity if not ignored
if msg.velocity != new_velocity:
msg.velocity = new_velocity
modified_messages += 1
# Generate the output file name by appending '_velfix' before the file extension
base, ext = os.path.splitext(input_path)
output_path = f"{base}_velfix{ext}"
try:
midi.save(output_path)
if verbose:
print(f"Saved modified MIDI to '{output_path}'.")
print(f"Total messages processed: {total_messages}")
print(f"Total messages modified: {modified_messages}")
if ignored_channels:
print(f"Total messages ignored (channels {sorted(ignored_channels)}): {ignored_messages}")
else:
print(f"Successfully saved modified MIDI to '{output_path}' with all velocities set to {new_velocity}.")
if ignored_channels:
print(f"Ignored velocities on channels: {sorted(ignored_channels)}.")
except IOError:
print(f"Error: Cannot write to output file '{output_path}'. Please check the path and permissions.")
sys.exit(1)
def validate_velocity(value):
"""
Validates that the velocity is an integer between 0 and 127.
Args:
value (str): The velocity value as a string.
Returns:
int: The validated velocity as an integer.
Raises:
argparse.ArgumentTypeError: If the value is not within the valid range.
"""
try:
ivalue = int(value)
except ValueError:
raise argparse.ArgumentTypeError(f"Velocity must be an integer between 0 and 127. '{value}' is invalid.")
if ivalue < 0 or ivalue > 127:
raise argparse.ArgumentTypeError(f"Velocity must be between 0 and 127. '{ivalue}' is out of range.")
return ivalue
def validate_channels(value):
"""
Validates that the channels are integers between 1 and 16.
Args:
value (str): Comma-separated channel numbers as a string.
Returns:
set[int]: A set of validated channel numbers.
Raises:
argparse.ArgumentTypeError: If any channel is not within the valid range.
"""
try:
channels = set(int(ch.strip()) for ch in value.split(',') if ch.strip() != '')
except ValueError:
raise argparse.ArgumentTypeError(f"Channels must be integers between 1 and 16, separated by commas. '{value}' is invalid.")
for ch in channels:
if ch < 1 or ch > 16:
raise argparse.ArgumentTypeError(f"Channel numbers must be between 1 and 16. '{ch}' is out of range.")
return channels
def parse_arguments():
"""
Parses command-line arguments.
Returns:
argparse.Namespace: The parsed arguments.
"""
parser = argparse.ArgumentParser(
description=(
"Set all velocities in a MIDI file to a specified number, optionally ignoring specified channels, "
"and save with '_velfix' appended to the filename."
)
)
parser.add_argument('input_midi', type=str, help='Path to the input MIDI file.')
parser.add_argument('velocity', type=validate_velocity, help='New velocity value (0-127).')
parser.add_argument(
'ignore_channels',
type=validate_channels,
nargs='?',
default=None,
help='(Optional) Comma-separated list of MIDI channels to ignore (e.g., "4,9"). Channels are 1-16.'
)
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)
set_all_velocities(
args.input_midi,
args.velocity,
ignored_channels=args.ignore_channels,
verbose=args.verbose
)
if __name__ == "__main__":
main()