Skip to content

Instantly share code, notes, and snippets.

@Dobby233Liu
Last active April 4, 2022 07:29
Show Gist options
  • Save Dobby233Liu/09ef70d99c623474ab0972412b4e66b2 to your computer and use it in GitHub Desktop.
Save Dobby233Liu/09ef70d99c623474ab0972412b4e66b2 to your computer and use it in GitHub Desktop.
Change all instruments in a MIDI file.
# 2022 Dobby233Liu
#
# This is free and unencumbered software released into the public domain.
#
# Anyone is free to copy, modify, publish, use, compile, sell, or
# distribute this software, either in source code form or as a compiled
# binary, for any purpose, commercial or non-commercial, and by any
# means.
#
# In jurisdictions that recognize copyright laws, the author or authors
# of this software dedicate any and all copyright interest in the
# software to the public domain. We make this dedication for the benefit
# of the public at large and to the detriment of our heirs and
# successors. We intend this dedication to be an overt act of
# relinquishment in perpetuity of all present and future rights to this
# software under copyright law.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
#
# For more information, please refer to <http://unlicense.org/>
"""
Change all instruments in a MIDI file.
This needs a custom version of python-midi: https://github.com/mgedmin/python-midi/tree/python3
"""
import midi
from midi import MetaEvent, ProgramChangeEvent, NoteOnEvent, NoteOffEvent
import argparse
import pathlib
import random
DRUM_CHANNEL = 10 - 1
def categorize_events(pattern):
event_list = [[] for _ in range(16)]
metaevent_list = []
for track_no, track in enumerate(pattern):
for event in track:
if isinstance(event, MetaEvent):
metaevent_list.append(event)
else:
event_list[event.channel].append((track_no, event))
return (metaevent_list, event_list)
def change_instruments(pattern, instrument_id, change_1st_event_only=False, modify_percussion=False):
randomize = False
if instrument_id < 0:
randomize = True
for channel_no, channel in enumerate(categorize_events(pattern)[1]):
if len(channel) <= 0 or (channel_no == DRUM_CHANNEL and not modify_percussion):
continue
inst_range = range(128)
if channel_no == DRUM_CHANNEL:
inst_range = range(35, 82)
channel_has_prog_change_event = False
tick0_has_change = False
track = None
for _track, event in channel:
# i don't know what i'm doing
if track is None:
track = _track
val = instrument_id
if randomize:
val = random.choice(inst_range)
if channel_no == DRUM_CHANNEL:
if isinstance(event, NoteOnEvent) or isinstance(event, NoteOffEvent):
event.pitch = val
elif isinstance(event, ProgramChangeEvent):
channel_has_prog_change_event = True
event.value = val
if event.tick == 0:
tick0_has_change = True
if change_1st_event_only:
break
# make sure the instrument is changed
if not channel_no == DRUM_CHANNEL and (not channel_has_prog_change_event or not tick0_has_change):
final_inst_id = instrument_id
if randomize:
final_inst_id = random.choice(inst_range)
# add the event
_event = ProgramChangeEvent(tick=0, channel=channel_no, value=final_inst_id)
pattern[track].insert(0, _event)
return pattern
def main():
parser = argparse.ArgumentParser(description='Change all instruments in a MIDI file.')
parser.add_argument('file', type=pathlib.Path,
help='source MIDI file')
parser.add_argument('id', type=int,
help='what instrument to change to (if < 0, performs randomization)')
parser.add_argument('output', type=pathlib.Path,
help='output file')
parser.add_argument('-i', '--change-1st-event-only', action='store_true',
help='change only the first instrument change event seen')
parser.add_argument('-p', '--percussion', action='store_true',
help='also change percussion')
args = parser.parse_args()
source_file, inst_id, output_file, change_1st_only, modify_percussion = (args.file, args.id, args.output, args.change_1st_event_only, args.percussion)
pattern = midi.read_midifile(source_file)
pattern = change_instruments(pattern, inst_id, change_1st_event_only=change_1st_only, modify_percussion=modify_percussion)
midi.write_midifile(output_file, pattern)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment