YAMI (Yet Another MIDI Importer)

Last night I started hacking away at my own tool for converting MIDI files into Sonic Pi code. I’ve looked at examples in the forum and the nice write-ups by @robin.newman, which I will continue learning from. For my specific needs I have a bunch of MIDI drum/keyboard/bass loop files that I’d like to load in and mix together. These will typically only constitute a few to several seconds of performance duration and my ideal is to convert directories of these loops into Sonic Pi code.

I’m using a Python package called Mido to help with parsing the messages and calculating time to beat conversions. My first pass dumpster fire is here. Currently the code takes a file I have and splits each drum type into a different loop. The output for my sample file looks like (disclaimer: the sample names were post-edited by hand, but future versions will map those in from an initial config):

use_bpm 98

live_loop :bass_drum_1_loop do
  sleep 0.0
  
  sample :drum_bass_hard
  sleep 1.5
  
  sample :drum_bass_hard
  sleep 0.4999999999999998
  
  sample :drum_bass_hard
  sleep 1.9999999999999998
  
  sample :drum_bass_hard
  sleep 1.4999999999999996
  
  sample :drum_bass_hard
  sleep 0.5000000000000009
  
  sample :drum_bass_hard
  sync :loop_window
  
end


live_loop :acoustic_snare_loop do
  sleep 3.0
  
  sample :drum_snare_hard
  sleep 3.5
  
  sample :drum_snare_hard
  sleep 0.9999999999999991
  
  sample :drum_snare_hard
  sync :loop_window
  
end


live_loop :high_floor_tom_loop do
  sleep 7.499999999999999
  
  sample :drum_tom_lo_hard
  sync :loop_window
  
end


live_loop :loop_window do
  8.times do
    sleep 1
  end
end

In future iterations I plan to get away from unrolling the timings into sample / sleep statements. I’d rather have more compact files that are just data structures with all the timings and data that I can then run with a helper function in a loop (pseudocode):

# Sonic Pi converted MIDI file patterns
Akron_Grv_1 = [...]
Akron_Grv_2 = [...]
Akron_Fill_1 = [...]

set drummer_pattern 'Akron_Grv_2'

define playPattern |name| do
  # iterate through pattern, sleeping and sampling at appropriate times
end

live_loop :drummer do
  playPattern(get['drummer_pattern'])
end