Initial test says I can merge midi files with single tracks into a multi-track midi file. Which is pretty much where I left off last post. Will start this one by looking at changing the instruments in the multi-track midi file. Including the one I exported from MuseScore that contains music data in Track 0.
Change Instruments on Multiple Tracks at One Time
I will start with the midi file that doesn’t have any note messages in Track 0. Doing so because I am absolutely certain I will need to refactor the code to get it to work with the 3 track version.
But first I need some new code (likely a function or two in one or more packages, plus a suitable if block in main.py).
em_files.chg_instr()
As I add code to this project, the more I realize how very messy that code is. But, I simply did not have enough knowledge to do any real planning. Learning as I go does tend to lead to a bit of a mess in design and, consequently, code. Expect there will be more mess to come.
A bit of a lengthy function and a message modification I hadn’t planned for until I saw the modified file loaded in MuseScore. That being the track name; I am guessing Basic Pitch added that message when doing the conversion from wave to midi. And, a fair bit of debugging I won’t get into. Not too mention likely way too much terminal output. But at the moment I like to see what is going on.
... ...
from mido import Message, MidiFile, MidiTrack
... ...
def chg_instr(args:dict[str, Union[str, int, bool]]) -> str:
""" Change the instrument for the specified tracks in a given midi file.
:param args: a dictionary of all the parameters required to generate the chord progression
keys are: whatdo, filepath_in, filepath_wv, filepath_mid, track_key, nbr_bars, play_track,
no_save_wv, no_save_mid, cp_type, cp_nbr, instr, r_oct, t_sig, s_rate, ot_s, ot_w, n_ot,
fs, dbg
requires: 'filepath_in', 'instr'
'filepath_in' is variable containing the path to the midi file
'instr' is variable containing a list of track to instrument tuples, (track #, new instrument name or program number)
:returns: name of new file saved after executing specified instrument changes
"""
nw_mid = ""
i_2_chg = parse_instr_arg(args["instr"])
print(f"\nchg_instr()\n\ti_2_chg: {i_2_chg}")
midi_fl = Path(args["filepath_in"])
fl_pth = midi_fl.parent
# print(f"\n{midi_fl},\n{fl_pth},\n{str(midi_fl)}")
mid = MidiFile(str(midi_fl), clip=True)
i_fnd = False
o_instrs = []
for ti, track in enumerate(mid.tracks):
for i, msg in enumerate(track):
if msg.type == 'track_name':
if ti in i_2_chg.keys():
print(f"\n\t{ti}: {i}, {msg}")
track[i] = msg.copy(name=i_2_chg[ti][1])
print(f"\t\tchanged to {track[i]}")
if msg.type == 'program_change':
# o_i = msg.program
# print(f"\t{msg}")
# print(f"\t{ti}: {i}, {msg} -> {msg.program} -> {type(msg.program)} -> {o_i} -> {type(o_i)})")
print(f"\t{ti}: {i}, {msg}")
old_instr = sfgs.get_instr_nm(msg.program)
if ti in i_2_chg.keys():
i_fnd = True
o_instrs.append(old_instr)
# Swap the instrument using .copy()
track[i] = msg.copy(program=i_2_chg[ti][0])
print(f"\t\tSwapped instrument {old_instr} to {i_2_chg[ti][1]} on Channel {msg.channel}")
else:
print(f"No instrument, {i_2_chg[ti]}, found in file")
if i_fnd:
f_stem = midi_fl.stem
n_lst = [ic[1] for ic in i_2_chg.values()]
i_sfx = "_".join(n_lst)
i_sfx = i_sfx.replace(" ", "-")
o_sfx = "_".join(o_instrs)
o_sfx = o_sfx.replace(" ", "-")
f_stem = f_stem.replace(f"_{o_sfx}", "")
ni_mid = Path(f"{fl_pth}/{f_stem}_{i_sfx}.mid")
mid.save(ni_mid)
return nw_mid
And, in main.py a small bit of additional code.
... ...
case "i_chg":
do_i_chg = True
args_ok, args = emui.get_args_do_w2m(cl_args)
... ...
# if do_i_chg or do_i_shw:
# mid = MidiFile(str(audio_fl), clip=True)
if do_i_chg:
nw_mid = emf.chg_instr(args)
if do_i_shw:
mid = MidiFile(str(audio_fl), clip=True)
In the terminal a test run produced the following. I am using the 3 track version of the midi file exported from MuseScore after loading the 3 track version my code generated.
PS R:\learn\e_m_311> uv run main.py -wd i_chg -fpi img/I-V-vi-IV_3-4_odd_sqr_o5_s0_s1_x1_t3maybe.mid -ift 0 00 -ift 1 00 -ift 2 43
... ...
command line args are ok: True
whatdo: i_chg
filepath_in: R:\learn\e_m_311/img/I-V-vi-IV_3-4_odd_sqr_o5_s0_s1_x1_t3maybe.mid
filepath_wv:
filepath_mid:
track_key: random
nbr_bars: 6
play_track: False
no_save_wv: False
no_save_mid: False
cp_type: random
cp_nbr: 0
instr: [['0', '00'], ['1', '00'], ['2', '43']]
fs: <midi2audio.FluidSynth object at 0x000002E1A3888290>
chg_instr()
i_2_chg: {0: (0, 'grand piano'), 1: (0, 'grand piano'), 2: (43, 'double bass')}
0: 0, MetaMessage('track_name', name='Electric Piano', time=0)
changed to MetaMessage('track_name', name='grand piano', time=0)
0: 10, program_change channel=0 program=4 time=0
Swapped instrument tine electric piano to grand piano on Channel 0
1: 0, MetaMessage('track_name', name='Electric Piano', time=0)
changed to MetaMessage('track_name', name='grand piano', time=0)
1: 8, program_change channel=1 program=4 time=0
Swapped instrument tine electric piano to grand piano on Channel 1
2: 0, MetaMessage('track_name', name='Electric Piano', time=0)
changed to MetaMessage('track_name', name='double bass', time=0)
2: 8, program_change channel=0 program=4 time=0
Swapped instrument tine electric piano to double bass on Channel 0
I have exported, in MuseScore, my modified 3 track file to a flac media file.
Once Again, Done
This is rather short for a post, but it is complete and I have not yet sorted out what I am going to attempt to code next. I would like to do something with respect to melody. But, I certainly don’t want to sort that out myself.
And, I have been looking at the system developed by Joseph Schillinger. A very mathematical approach to making music. But I currently don’t have a clue how to code any of it nor whether I can put anything I do code to good use. At present, I truly have my doubts that I can generate any such value.
Resources
and if you have the wherewithall, Schillinger System of Musical Composition, PDF
How to Write a Melody?, YouTube video, liked this but don’t think I want to get into doing what she was teaching