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