aboutsummaryrefslogtreecommitdiff
path: root/mkiv.py
diff options
context:
space:
mode:
authorcsguest <csguest@yyyyy.cslabs.clarkson.edu>2015-06-12 08:58:04 -0400
committercsguest <csguest@yyyyy.cslabs.clarkson.edu>2015-06-12 08:58:04 -0400
commitb49521e6a170b16d551f144ed6e652eb9ce8bb79 (patch)
treeba92fba115057c766329f322b320d14f84b2b72c /mkiv.py
First (terrible) commit
Diffstat (limited to 'mkiv.py')
-rw-r--r--mkiv.py156
1 files changed, 156 insertions, 0 deletions
diff --git a/mkiv.py b/mkiv.py
new file mode 100644
index 0000000..bddbe8b
--- /dev/null
+++ b/mkiv.py
@@ -0,0 +1,156 @@
+'''
+itl_chorus -- ITL Chorus Suite
+mkiv -- Make Intervals
+
+This simple script (using python-midi) reads a MIDI file and makes an interval
+(.iv) file (actually XML) that contains non-overlapping notes.
+
+TODO:
+-Reserve channels by track
+-Reserve channels by MIDI channel
+-Pitch limits for channels
+-MIDI Control events
+'''
+
+import xml.etree.ElementTree as ET
+import midi
+import sys
+import os
+
+pat = midi.read_midifile(sys.argv[1])
+iv = ET.Element('iv')
+iv.set('version', '1')
+iv.set('src', os.path.basename(sys.argv[1]))
+
+##### Merge events from all tracks into one master list, annotated with track and absolute times #####
+print 'Merging events...'
+
+class MergeEvent(object):
+ __slots__ = ['ev', 'tidx', 'abstime']
+ def __init__(self, ev, tidx, abstime):
+ self.ev = ev
+ self.tidx = tidx
+ self.abstime = abstime
+ def __repr__(self):
+ return '<ME %r in %d @%f>'%(self.ev, self.tidx, self.abstime)
+
+events = []
+bpm_at = {0: 120}
+
+for tidx, track in enumerate(pat):
+ abstime = 0
+ absticks = 0
+ for ev in track:
+ if isinstance(ev, midi.SetTempoEvent):
+ absticks += ev.tick
+ bpm_at[absticks] = ev.bpm
+ else:
+ if isinstance(ev, midi.NoteOnEvent) and ev.velocity == 0:
+ ev.__class__ = midi.NoteOffEvent #XXX Oww
+ bpm = filter(lambda pair: pair[0] <= absticks, bpm_at.items())[-1][1]
+ abstime += (60.0 * ev.tick) / (bpm * pat.resolution)
+ absticks += ev.tick
+ events.append(MergeEvent(ev, tidx, abstime))
+
+print 'Sorting events...'
+
+events.sort(key = lambda ev: ev.abstime)
+
+##### Use merged events to construct a set of streams with non-overlapping durations #####
+print 'Generating streams...'
+
+class DurationEvent(MergeEvent):
+ __slots__ = ['duration']
+ def __init__(self, me, dur):
+ MergeEvent.__init__(self, me.ev, me.tidx, me.abstime)
+ self.duration = dur
+
+class NoteStream(object):
+ __slots__ = ['history', 'active']
+ def __init__(self):
+ self.history = []
+ self.active = None
+ def IsActive(self):
+ return self.active is not None
+ def Activate(self, mev):
+ self.active = mev
+ def Deactivate(self, mev):
+ self.history.append(DurationEvent(self.active, mev.abstime - self.active.abstime))
+ self.active = None
+ def WouldDeactivate(self, mev):
+ if not self.IsActive():
+ return False
+ return mev.ev.pitch == self.active.ev.pitch and mev.tidx == self.active.tidx
+
+notestreams = []
+auxstream = []
+
+for mev in events:
+ if isinstance(mev.ev, midi.NoteOnEvent):
+ for stream in notestreams:
+ if not stream.IsActive():
+ stream.Activate(mev)
+ break
+ else:
+ stream = NoteStream()
+ notestreams.append(stream)
+ stream.Activate(mev)
+ elif isinstance(mev.ev, midi.NoteOffEvent):
+ for stream in notestreams:
+ if stream.WouldDeactivate(mev):
+ stream.Deactivate(mev)
+ break
+ else:
+ print 'WARNING: Did not match %r with any stream deactivation.'%(mev,)
+ else:
+ auxstream.append(mev)
+
+lastabstime = events[-1].abstime
+
+for ns in notestreams:
+ if not ns:
+ print 'WARNING: Active notes at end of playback.'
+ ns.Deactivate(MergeEvent(ns.active, ns.active.tidx, lastabstime))
+
+print 'Generated %d streams'%(len(notestreams),)
+
+##### Write to XML and exit #####
+
+ivmeta = ET.SubElement(iv, 'meta')
+ivbpms = ET.SubElement(ivmeta, 'bpms')
+abstime = 0
+prevticks = 0
+prev_bpm = 120
+for absticks, bpm in sorted(bpm_at.items(), key = lambda pair: pair[0]):
+ abstime += ((absticks - prevticks) * 60.0) / (prev_bpm * pat.resolution)
+ prevticks = absticks
+ ivbpm = ET.SubElement(ivbpms, 'bpm')
+ ivbpm.set('bpm', str(bpm))
+ ivbpm.set('ticks', str(absticks))
+ ivbpm.set('time', str(abstime))
+
+ivstreams = ET.SubElement(iv, 'streams')
+
+for ns in notestreams:
+ ivns = ET.SubElement(ivstreams, 'stream')
+ ivns.set('type', 'ns')
+ for note in ns.history:
+ ivnote = ET.SubElement(ivns, 'note')
+ ivnote.set('pitch', str(note.ev.pitch))
+ ivnote.set('vel', str(note.ev.velocity))
+ ivnote.set('time', str(note.abstime))
+ ivnote.set('dur', str(note.duration))
+
+ivaux = ET.SubElement(ivstreams, 'stream')
+ivaux.set('type', 'aux')
+
+fw = midi.FileWriter()
+fw.RunningStatus = None # XXX Hack
+
+for mev in auxstream:
+ ivev = ET.SubElement(ivaux, 'ev')
+ ivev.set('time', str(mev.abstime))
+ ivev.set('data', repr(fw.encode_midi_event(mev.ev)))
+
+print 'Done.'
+open(os.path.splitext(os.path.basename(sys.argv[1]))[0]+'.iv', 'w').write(ET.tostring(iv))