Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
zblack committed Aug 6, 2012
0 parents commit 447e0fe
Show file tree
Hide file tree
Showing 2 changed files with 162 additions and 0 deletions.
40 changes: 40 additions & 0 deletions README
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
* Polyphony the cheap (and a bit lengthy) way *

This Python script converts a midi file containing a polyphonic track into a multitrack one (one voice per track).
This can be useful if you need to render polyphony with an outboard mono synth.

---------------------------------------------------------
Installation:

- get python if not already present (try "python" at command line)
http://www.python.org/download/
I used 2.6, chances are it may work with others, likely not 3.X though.

- get this mighty library from pichenettes' git
https://raw.github.com/pichenettes/avril-firmware_tools/master/midi/midifile.py
(save as midifile.py)

- get the converter script
https://raw.github.com/zblack/p2m/master/p2m.py
(save as p2m.py)

put the two saved files in a proper folder where you plan to do conversions.

---------------------------------------------------------
Usage:

python p2m.py -i somefilename.mid

it will create a new file called somefilename_multi.mid

---------------------------------------------------------
Tipical workflow:

- MIDI export the track you want to convert from your DAW software in the conversion folder
- execute the script
- reimport the new "multi" midi file into the DAW software, if asked to merge tracks say no because it will make this procedure perfectly useless.
- create as many mono audio tracks as the midi tracks you just imported.
- one track at a time render the midi using the outboard gear (solo and play one midi track while recording to a soloed audio track at a time).
- optional: route the audio tracks generated to a submix fader using some panning to give spatiality.

I made very little test, chances are not all cases covered, just let me know and I can eventually fix bugs.
122 changes: 122 additions & 0 deletions p2m.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#!/usr/bin/python2.6

#Polyphonic to Multitrack midi file Converter
#Splits all the voices of a midi file into several mono tracks and writes the result in the output midi file
#It will produce as many tracks as the maximum polyphony used at any given time in the whole file
#no limits on the voices, you can sprawl on the keyboard :)
#usage: python p2m.py -i inputfile

import os
import sys
import optparse
from midifile import *

class Voices(object):
def __init__(self):
#list of allocated voices/tracks, contains note numbers, 0 means a now free but previously played voice
self.voices = list()

def SlotOf(self, note):
return self.voices.index(note)

#looks for an empty slot, if not found allocates a new track
#returns the slot number
def NoteOn(self, d, note, w):
try:
i = self.SlotOf(0)
except:
self.voices.append(note)
i = len(self.voices) - 1
if i > 0:
w.AddTrack()

self.voices[i] = note
return i

def NoteOff(self, note):
try:
i = self.SlotOf(note)
self.voices[i] = 0
return i
#it should never happen, but just in case it won't wreak havoc
except:
return -1

class Converter(object):
def __init__(self):
self.v = Voices()

def Convert(self, ifname, ofname):
r = Reader()
w = Writer()

#a first track always needed for a no note event occurring before a note event
w.AddTrack()

fin = file(ifname, 'rb')
r.Read(fin)

#Considering the limited scope of this script, tempo info tracks will be discarded
for t in r.tracks:
#identify a playable track
playabletrack = False
for _, e in t:
if isinstance(e, NoteOnEvent):
playabletrack = True
break

if playabletrack:
for d, e in t:
#voice specific events
if isinstance(e, ChannelEvent):
if isinstance(e, NoteOnEvent):
i = self.v.NoteOn(d, e.note, w)
#a bit unfair, but had not choice
w._tracks[i].AddEvent(d, e)
elif isinstance(e, NoteOffEvent):
i = self.v.NoteOff(e.note)
if i >= 0:
w._tracks[i].AddEvent(d, e)
elif isinstance(e, KeyAftertouchEvent):
i = self.v.SlotOf(e.note)
w._tracks[i].AddEvent(d, e)
#any else channel event must be copied as is and propagated to the other tracks
else:
for i in 0..len(self.w._tracks) - 1:
w._tracks[i].AddEvent(d, e)
#everything else except track names will be copied to the first track
else:
if isinstance(e, TrackNameEvent):
trackname = e.text
else:
w._tracks[0].AddEvent(d, e)

for t in w._tracks:
t.AddEvent(1, TrackNameEvent(trackname + '_' + str(w._tracks.index(t) + 1)))

w._ppq = r.ppq
fout = file(ofname, 'wb')
w.Write(fout, format=1)
fin.close()
fout.close()

return len(w._tracks)

if __name__ == '__main__':
parser = optparse.OptionParser()
parser.add_option(
'-i',
'--input_file',
dest='input_file',
default='Untitled.mid',
help='input file FILE',
metavar='FILE')

options, _ = parser.parse_args()

c = Converter()
ifname = options.input_file
ofname = os.path.splitext(ifname)[0] + '_multi.mid'
i = c.Convert(ifname, ofname)

sys.stdout.write(str(i) + ' Mono Tracks Generated into ' + ofname)

0 comments on commit 447e0fe

Please sign in to comment.