From b6ab9fcbec899e345d81554d60111e95cf9ce466 Mon Sep 17 00:00:00 2001 From: Grissess Date: Wed, 20 Apr 2016 04:13:13 -0400 Subject: Bugfixes, graphics, and new generators --- mkiv.py | 77 +++++++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 19 deletions(-) (limited to 'mkiv.py') diff --git a/mkiv.py b/mkiv.py index 2293861..0d2bfa0 100644 --- a/mkiv.py +++ b/mkiv.py @@ -6,10 +6,8 @@ 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 +-Percussion ''' import xml.etree.ElementTree as ET @@ -27,10 +25,12 @@ parser.add_option('-c', '--preserve-channels', dest='chanskeep', action='store_t parser.add_option('-T', '--track-split', dest='tracks', action='append_const', const=TRACKS, help='Ensure all tracks are on non-mutual streams') parser.add_option('-t', '--track', dest='tracks', action='append', help='Reserve an exclusive set of streams for certain conditions (try --help-conds)') parser.add_option('--help-conds', dest='help_conds', action='store_true', help='Print help on filter conditions for streams') -parser.add_option('-P', '--no-percussion', dest='no_perc', action='store_true', help='Don\'t try to filter percussion events out') +parser.add_option('-P', '--percussion', dest='perc', help='Which percussion standard to use to automatically filter to "perc" (GM, GM2, or none)') parser.add_option('-f', '--fuckit', dest='fuckit', action='store_true', help='Use the Python Error Steamroller when importing MIDIs (useful for extended formats)') parser.add_option('-n', '--target-num', dest='repeaterNumber', type='int', help='Target count of devices') -parser.set_defaults(tracks=[], repeaterNumber=1) +parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Be verbose; show important parts about the MIDI scheduling process') +parser.add_option('-d', '--debug', dest='debug', action='store_true', help='Debugging output; show excessive output about the MIDI scheduling process') +parser.set_defaults(tracks=[], repeaterNumber=1, perc='GM') options, args = parser.parse_args() if options.help_conds: @@ -40,9 +40,14 @@ Every filter is an expression; internally, this expression is evaluated as the b The "ev" object will be a MergeEvent with the following properties: -ev.tidx: the originating track index (starting at 0) -ev.abstime: the real time in seconds of this event relative to the beginning of playback +-ev.bank: the selected bank (all bits) +-ev.prog: the selected program -ev.ev: a midi.NoteOnEvent: -ev.ev.pitch: the MIDI pitch -ev.ev.velocity: the MIDI velocity + -ev.ev.channel: the MIDI channel + +All valid Python expressions are accepted. Take care to observe proper shell escaping. Specifying a -t = will group all streams under a filter; if the part is omitted, no group will be added. For example: @@ -122,31 +127,59 @@ for fname in args: print 'Merging events...' class MergeEvent(object): - __slots__ = ['ev', 'tidx', 'abstime'] - def __init__(self, ev, tidx, abstime): + __slots__ = ['ev', 'tidx', 'abstime', 'bank', 'prog'] + def __init__(self, ev, tidx, abstime, bank, prog): self.ev = ev self.tidx = tidx self.abstime = abstime + self.bank = bank + self.prog = prog def __repr__(self): return ''%(self.ev, self.tidx, self.abstime) events = [] bpm_at = {0: 120} + cur_bank = [[0 for i in range(16)] for j in range(len(pat))] + cur_prog = [[0 for i in range(16)] for j in range(len(pat))] + chg_bank = [[0 for i in range(16)] for j in range(len(pat))] + chg_prog = [[0 for i in range(16)] for j in range(len(pat))] + ev_cnts = [[0 for i in range(16)] for j in range(len(pat))] + tnames = [''] * len(pat) for tidx, track in enumerate(pat): abstime = 0 absticks = 0 for ev in track: + if options.debug: + print ev if isinstance(ev, midi.SetTempoEvent): absticks += ev.tick bpm_at[absticks] = ev.bpm - else: + elif isinstance(ev, midi.ProgramChangeEvent): + cur_prog[tidx][ev.channel] = ev.value + chg_prog[tidx][ev.channel] += 1 + elif isinstance(ev, midi.ControlChangeEvent): + if ev.control == 0: + cur_bank[tidx][ev.channel] = (0x3F80 & cur_bank[tidx][ev.channel]) | ev.value + chg_bank[tidx][ev.channel] += 1 + elif ev.control == 32: + cur_bank[tidx][ev.channel] = (0x3F & cur_bank[tidx][ev.channel]) | (ev.value << 7) + chg_bank[tidx][ev.channel] += 1 + elif isinstance(ev, midi.TrackNameEvent): + tnames[tidx] = ev.text + elif isinstance(ev, midi.Event): if isinstance(ev, midi.NoteOnEvent) and ev.velocity == 0: ev.__class__ = midi.NoteOffEvent #XXX Oww bpm = filter(lambda pair: pair[0] <= absticks, sorted(bpm_at.items(), key=lambda pair: pair[0]))[-1][1] abstime += (60.0 * ev.tick) / (bpm * pat.resolution) absticks += ev.tick - events.append(MergeEvent(ev, tidx, abstime)) + events.append(MergeEvent(ev, tidx, abstime, cur_bank[tidx][ev.channel], cur_prog[tidx][ev.channel])) + ev_cnts[tidx][ev.channel] += 1 + + if options.verbose: + print 'Track name, event count, final banks, bank changes, final programs, program changes:' + for tidx, tname in enumerate(tnames): + print tidx, ':', tname, ',', ','.join(map(str, ev_cnts[tidx])), ',', ','.join(map(str, cur_bank[tidx])), ',', ','.join(map(str, chg_bank[tidx])), ',', ','.join(map(str, cur_prog[tidx])), ',', ','.join(map(str, chg_prog[tidx])) print 'Sorting events...' @@ -158,7 +191,7 @@ for fname in args: class DurationEvent(MergeEvent): __slots__ = ['duration'] def __init__(self, me, dur): - MergeEvent.__init__(self, me.ev, me.tidx, me.abstime) + MergeEvent.__init__(self, me.ev, me.tidx, me.abstime, me.bank, me.prog) self.duration = dur class NoteStream(object): @@ -200,8 +233,13 @@ for fname in args: notegroups = [] auxstream = [] - if not options.no_perc: - notegroups.append(NSGroup(filter = lambda mev: mev.ev.channel == 10, name='perc')) + if options.perc and options.perc != 'none': + if options.perc == 'GM': + notegroups.append(NSGroup(filter = lambda mev: mev.ev.channel == 9, name='perc')) + elif options.perc == 'GM2': + notegroups.append(NSGroup(filter = lambda mev: mev.bank == 15360, name='perc')) + else: + print 'Unrecognized --percussion option %r; should be GM, GM2, or none'%(options.perc,) for spec in options.tracks: if spec is TRACKS: @@ -214,9 +252,10 @@ for fname in args: name = None notegroups.append(NSGroup(filter = eval("lambda ev: "+spec), name = name)) - print 'Initial group mappings:' - for group in notegroups: - print ('' if group.name is None else group.name), '<=', group.filter + if options.verbose: + print 'Initial group mappings:' + for group in notegroups: + print ('' if group.name is None else group.name) for mev in events: if isinstance(mev.ev, midi.NoteOnEvent): @@ -250,9 +289,10 @@ for fname in args: print 'WARNING: Active notes at end of playback.' ns.Deactivate(MergeEvent(ns.active, ns.active.tidx, lastabstime)) - print 'Final group mappings:' - for group in notegroups: - print ('' if group.name is None else group.name), '<=', group.filter, '(', len(group.streams), 'streams)' + if options.verbose: + print 'Final group mappings:' + for group in notegroups: + print ('' if group.name is None else group.name), '<=', '(', len(group.streams), 'streams)' print 'Generated %d streams in %d groups'%(sum(map(lambda x: len(x.streams), notegroups)), len(notegroups)) print 'Playtime:', lastabstime, 'seconds' @@ -289,7 +329,6 @@ for fname in args: ivnote.set('time', str(note.abstime)) ivnote.set('dur', str(note.duration)) x+=1 - print x if(x>=options.repeaterNumber and options.repeaterNumber!=1): break if(x>=options.repeaterNumber and options.repeaterNumber!=1): -- cgit v1.2.3-70-g09d2