Jelajahi Sumber

Slack time, 24-bit color client terminal printing, and default T:perc routing

Graham Northup 7 tahun lalu
induk
melakukan
7654ad67b4
5 mengubah file dengan 100 tambahan dan 18 penghapusan
  1. 2 1
      broadcast.py
  2. 31 11
      client.py
  3. 32 2
      drums.py
  4. 34 3
      mkiv.py
  5. 1 1
      shiv.py

+ 2 - 1
broadcast.py

@@ -33,6 +33,7 @@ parser.add_option('-s', '--silence', dest='silence', action='store_true', help='
 parser.add_option('-S', '--seek', dest='seek', type='float', help='Start time in seconds (scaled by --factor)')
 parser.add_option('-f', '--factor', dest='factor', type='float', help='Rescale time by this factor (0<f<1 are faster; 0.5 is twice the speed, 2 is half)')
 parser.add_option('-r', '--route', dest='routes', action='append', help='Add a routing directive (see --route-help)')
+parser.add_option('--clear-routes', dest='routes', action='store_const', const=[], help='Clear routes previously specified (including the default)')
 parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Be verbose; dump events and actual time (can slow down performance!)')
 parser.add_option('-W', '--wait-time', dest='wait_time', type='float', help='How long to wait between pings for clients to initially respond (delays all broadcasts)')
 parser.add_option('--tries', dest='tries', type='int', help='Number of ping packets to send')
@@ -50,7 +51,7 @@ parser.add_option('--pg-fullscreen', dest='fullscreen', action='store_true', hel
 parser.add_option('--pg-width', dest='pg_width', type='int', help='Width of the pygame window')
 parser.add_option('--pg-height', dest='pg_height', type='int', help='Width of the pygame window')
 parser.add_option('--help-routes', dest='help_routes', action='store_true', help='Show help about routing directives')
-parser.set_defaults(routes=[], random=0.0, rand_low=80, rand_high=2000, live=None, factor=1.0, duration=0.25, volume=1.0, wait_time=0.1, tries=5, play=[], transpose=0, seek=0.0, bind_addr='', ports=[13676],  pg_width = 0, pg_height = 0, number=-1, pcmlead=0.1)
+parser.set_defaults(routes=['T:DRUM=!perc,0'], random=0.0, rand_low=80, rand_high=2000, live=None, factor=1.0, duration=0.25, volume=1.0, wait_time=0.1, tries=5, play=[], transpose=0, seek=0.0, bind_addr='', ports=[13676, 13677],  pg_width = 0, pg_height = 0, number=-1, pcmlead=0.1)
 options, args = parser.parse_args()
 
 if options.help_routes:

+ 31 - 11
client.py

@@ -13,6 +13,7 @@ import array
 import random
 import threading
 import thread
+import colorsys
 
 from packet import Packet, CMD, stoi
 
@@ -35,6 +36,7 @@ parser.add_option('--pg-no-colback', dest='no_colback', action='store_true', hel
 parser.add_option('--pg-low-freq', dest='low_freq', type='int', default=40, help='Low frequency for colored background')
 parser.add_option('--pg-high-freq', dest='high_freq', type='int', default=1500, help='High frequency for colored background')
 parser.add_option('--pg-log-base', dest='log_base', type='int', default=2, help='Logarithmic base for coloring (0 to make linear)')
+parser.add_option('--counter-modulus', dest='counter_modulus', type='int', default=16, help='Number of packet events in period of the terminal color scroll on the left margin')
 
 options, args = parser.parse_args()
 
@@ -64,6 +66,16 @@ QUEUED_PCM = ''
 def lin_interp(frm, to, p):
     return p*to + (1-p)*frm
 
+def rgb_for_freq_amp(f, a):
+    pitchval = float(f - options.low_freq) / (options.high_freq - options.low_freq)
+    if options.log_base == 0:
+        try:
+            pitchval = math.log(pitchval) / math.log(options.log_base)
+        except ValueError:
+            pass
+    bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * (a ** 2), 1.0)
+    return [int(i*255) for i in bgcol]
+
 # GUIs
 
 GUIs = {}
@@ -76,7 +88,6 @@ def GUI(f):
 def pygame_notes():
     import pygame
     import pygame.gfxdraw
-    import colorsys
     pygame.init()
 
     dispinfo = pygame.display.Info()
@@ -124,14 +135,7 @@ def pygame_notes():
                 FREQ = FREQS[i]
                 AMP = AMPS[i]
                 if FREQ > 0:
-                    pitchval = float(FREQ - options.low_freq) / (options.high_freq - options.low_freq)
-                    if options.log_base == 0:
-                        try:
-                            pitchval = math.log(pitchval) / math.log(options.log_base)
-                        except ValueError:
-                            pass
-                    bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * ((AMP / float(MAX)) ** 2), 1.0)
-                    bgcol = [int(j*255) for j in bgcol]
+                    bgcol = rgb_for_freq_amp(FREQ, float(AMP) / MAX)
                 else:
                     bgcol = (0, 0, 0)
                 #print i, ':', pitchval
@@ -420,6 +424,7 @@ sock.bind(('', PORT))
 
 #signal.signal(signal.SIGALRM, sigalrm)
 
+counter = 0
 while True:
     data = ''
     while not data:
@@ -428,12 +433,17 @@ while True:
         except socket.error:
             pass
     pkt = Packet.FromStr(data)
-    print 'From', cli, 'command', pkt.cmd
+    crgb = [int(i*255) for i in colorsys.hls_to_rgb((float(counter) / options.counter_modulus) % 1.0, 0.5, 1.0)]
+    print '\x1b[38;2;{};{};{}m#'.format(*crgb),
+    counter += 1
+    print '\x1b[mFrom', cli, 'command', pkt.cmd,
     if pkt.cmd == CMD.KA:
-        pass
+        print '\x1b[37mKA'
     elif pkt.cmd == CMD.PING:
         sock.sendto(data, cli)
+        print '\x1b[1;33mPING'
     elif pkt.cmd == CMD.QUIT:
+        print '\x1b[1;31mQUIT'
         break
     elif pkt.cmd == CMD.PLAY:
         voice = pkt.data[4]
@@ -441,6 +451,15 @@ while True:
         FREQS[voice] = pkt.data[2]
         AMPS[voice] = MAX * max(min(pkt.as_float(3), 1.0), 0.0)
         EXPIRATIONS[voice] = time.time() + dur
+        vrgb = [int(i*255) for i in colorsys.hls_to_rgb(float(voice) / STREAMS * 2.0 / 3.0, 0.5, 1.0)]
+        frgb = rgb_for_freq_amp(pkt.data[2], pkt.as_float(3))
+        print '\x1b[1;32mPLAY',
+        print '\x1b[1;38;2;{};{};{}mVOICE'.format(*vrgb), '{:03}'.format(voice),
+        print '\x1b[1;38;2;{};{};{}mFREQ'.format(*frgb), '{:04}'.format(pkt.data[2]), 'AMP', '%08.6f'%pkt.as_float(3),
+        if pkt.data[0] == 0 and pkt.data[1] == 0:
+            print '\x1b[1;35mSTOP!!!'
+        else:
+            print '\x1b[1;36mDUR', '%08.6f'%dur
         #signal.setitimer(signal.ITIMER_REAL, dur)
     elif pkt.cmd == CMD.CAPS:
         data = [0] * 8
@@ -449,6 +468,7 @@ while True:
         for i in xrange(len(UID)/4 + 1):
             data[i+2] = stoi(UID[4*i:4*(i+1)])
         sock.sendto(str(Packet(CMD.CAPS, *data)), cli)
+        print '\x1b[1;34mCAPS'
     elif pkt.cmd == CMD.PCM:
         fdata = data[4:]
         fdata = struct.pack('16i', *[i<<16 for i in struct.unpack('16h', fdata)])

+ 32 - 2
drums.py

@@ -6,6 +6,7 @@ import wave
 import cStringIO as StringIO
 import array
 import time
+import colorsys
 
 from packet import Packet, CMD, stoi, OBLIGATE_POLYPHONE
 
@@ -19,6 +20,10 @@ parser.add_option('-p', '--port', dest='port', default=13676, type='int', help='
 parser.add_option('--repeat', dest='repeat', action='store_true', help='If a note plays longer than a sample length, keep playing the sample')
 parser.add_option('--cut', dest='cut', action='store_true', help='If a note ends within a sample, stop playing that sample immediately')
 parser.add_option('-n', '--max-voices', dest='max_voices', default=-1, type='int', help='Only support this many notes playing simultaneously (earlier ones get dropped)')
+parser.add_option('--pg-low-freq', dest='low_freq', type='int', default=40, help='Low frequency for colored background')
+parser.add_option('--pg-high-freq', dest='high_freq', type='int', default=1500, help='High frequency for colored background')
+parser.add_option('--pg-log-base', dest='log_base', type='int', default=2, help='Logarithmic base for coloring (0 to make linear)')
+parser.add_option('--counter-modulus', dest='counter_modulus', type='int', default=16, help='Number of packet events in period of the terminal color scroll on the left margin')
 
 options, args = parser.parse_args()
 
@@ -31,6 +36,16 @@ if not args:
     parser.print_usage()
     exit(1)
 
+def rgb_for_freq_amp(f, a):
+    pitchval = float(f - options.low_freq) / (options.high_freq - options.low_freq)
+    if options.log_base == 0:
+        try:
+            pitchval = math.log(pitchval) / math.log(options.log_base)
+        except ValueError:
+            pass
+    bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * (a ** 2), 1.0)
+    return [int(i*255) for i in bgcol]
+
 DRUMS = {}
 
 for fname in args:
@@ -134,6 +149,7 @@ sock.bind(('', options.port))
 
 #signal.signal(signal.SIGALRM, sigalrm)
 
+counter = 0
 while True:
     data = ''
     while not data:
@@ -142,12 +158,17 @@ while True:
         except socket.error:
             pass
     pkt = Packet.FromStr(data)
-    print 'From', cli, 'command', pkt.cmd
+    crgb = [int(i*255) for i in colorsys.hls_to_rgb((float(counter) / options.counter_modulus) % 1.0, 0.5, 1.0)]
+    print '\x1b[38;2;{};{};{}m#'.format(*crgb),
+    counter += 1
+    print '\x1b[mFrom', cli, 'command', pkt.cmd,
     if pkt.cmd == CMD.KA:
-        pass
+        print '\x1b[37mKA'
     elif pkt.cmd == CMD.PING:
         sock.sendto(data, cli)
+        print '\x1b[1;33mPING'
     elif pkt.cmd == CMD.QUIT:
+        print '\x1b[1;31mQUIT'
         break
     elif pkt.cmd == CMD.PLAY:
         frq = pkt.data[2]
@@ -167,6 +188,14 @@ while True:
         if options.max_voices >= 0:
             while len(PLAYING) > options.max_voices:
                 PLAYING.pop(0)
+        frgb = rgb_for_freq_amp(pkt.data[2], pkt.as_float(3))
+        print '\x1b[1;32mPLAY',
+        print '\x1b[1;34mVOICE', '{:03}'.format(pkt.data[4]),
+        print '\x1b[1;38;2;{};{};{}mFREQ'.format(*frgb), '{:04}'.format(pkt.data[2]), 'AMP', '%08.6f'%pkt.as_float(3),
+        if pkt.data[0] == 0 and pkt.data[1] == 0:
+            print '\x1b[1;35mSTOP!!!'
+        else:
+            print '\x1b[1;36mDUR', '%08.6f'%dur
         #signal.setitimer(signal.ITIMER_REAL, dur)
     elif pkt.cmd == CMD.CAPS:
         data = [0] * 8
@@ -175,6 +204,7 @@ while True:
         for i in xrange(len(options.uid)/4 + 1):
             data[i+2] = stoi(options.uid[4*i:4*(i+1)])
         sock.sendto(str(Packet(CMD.CAPS, *data)), cli)
+        print '\x1b[1;34mCAPS'
 #    elif pkt.cmd == CMD.PCM:
 #        fdata = data[4:]
 #        fdata = struct.pack('16i', *[i<<16 for i in struct.unpack('16h', fdata)])

+ 34 - 3
mkiv.py

@@ -42,10 +42,11 @@ parser.add_option('--string-rate-off', dest='stringoffrate', type='float', help=
 parser.add_option('--string-threshold', dest='stringthres', type='float', help='Amplitude (as fraction of original) at which point the string model event is terminated')
 parser.add_option('--tempo', dest='tempo', help='Adjust interpretation of tempo (try "f1"/"global", "f2"/"track")')
 parser.add_option('--epsilon', dest='epsilon', type='float', help='Don\'t consider overlaps smaller than this number of seconds (which regularly happen due to precision loss)')
+parser.add_option('--slack', dest='slack', type='float', help='Inflate the duration of events by this much when scheduling them--this is for clients which need time to release their streams')
 parser.add_option('--vol-pow', dest='vol_pow', type='float', help='Exponent to raise volume changes (adjusts energy per delta volume)')
 parser.add_option('-0', '--keep-empty', dest='keepempty', action='store_true', help='Keep (do not cull) events with 0 duration in the output file')
 parser.add_option('--no-text', dest='no_text', action='store_true', help='Disable text streams (useful for unusual text encodings)')
-parser.set_defaults(tracks=[], perc='GM', deviation=2, tempo='global', modres=0.005, modfdev=2.0, modffreq=8.0, modadev=0.5, modafreq=8.0, stringres=0, stringmax=1024, stringrateon=0.7, stringrateoff=0.4, stringthres=0.02, epsilon=1e-12, vol_pow=2)
+parser.set_defaults(tracks=[], perc='GM', deviation=2, tempo='global', modres=0.005, modfdev=2.0, modffreq=8.0, modadev=0.5, modafreq=8.0, stringres=0, stringmax=1024, stringrateon=0.7, stringrateoff=0.4, stringthres=0.02, epsilon=1e-12, slack=0.0, vol_pow=2)
 options, args = parser.parse_args()
 if options.tempo == 'f1':
     options.tempo == 'global'
@@ -315,12 +316,13 @@ for fname in args:
     print 'Generating streams...'
 
     class DurationEvent(MergeEvent):
-        __slots__ = ['duration', 'pitch', 'modwheel', 'ampl']
+        __slots__ = ['duration', 'real_duration', 'pitch', 'modwheel', 'ampl']
         def __init__(self, me, pitch, ampl, dur, modwheel=0):
             MergeEvent.__init__(self, me.ev, me.tidx, me.abstime, me.bank, me.prog, me.mw)
             self.pitch = pitch
             self.ampl = ampl
             self.duration = dur
+            self.real_duration = dur
             self.modwheel = modwheel
 
         def __repr__(self):
@@ -482,6 +484,35 @@ for fname in args:
                 print 'WARNING: Active notes at end of playback.'
                 ns.Deactivate(MergeEvent(ns.active, ns.active.tidx, lastabstime))
 
+    if options.slack > 0:
+        print 'Adding slack time...'
+
+        slack_evs = []
+        for group in notegroups:
+            for ns in group.streams:
+                for dev in ns.history:
+                    dev.duration += options.slack
+                    slack_evs.append(dev)
+
+        print 'Resorting all streams...'
+        for group in notegroups:
+            group.streams = []
+
+        for dev in slack_evs:
+            for group in notegroups:
+                if not group.filter(dev):
+                    continue
+                for ns in group.streams:
+                    if dev.abstime >= ns.history[-1].abstime + ns.history[-1].duration:
+                        ns.history.append(dev)
+                        break
+                else:
+                    group.streams.append(NoteStream())
+                    group.streams[-1].history.append(dev)
+                break
+            else:
+                print 'WARNING: No stream accepts event', dev
+
     if options.modres > 0:
         print 'Resolving modwheel events...'
         ev_cnt = 0
@@ -685,7 +716,7 @@ for fname in args:
                             ivnote.set('vel', str(int(note.ampl * 127.0)))
                             ivnote.set('ampl', str(note.ampl))
                             ivnote.set('time', str(note.abstime))
-                            ivnote.set('dur', str(note.duration))
+                            ivnote.set('dur', str(note.real_duration))
 
     if not options.no_text:
         ivtext = ET.SubElement(ivstreams, 'stream', type='text')

+ 1 - 1
shiv.py

@@ -219,7 +219,7 @@ for fname in args:
         notes = stream.findall('note')
         for note in notes:
             pitch = float(note.get('pitch'))
-            ampl = float(note.get('ampl', float(note.get('vel', 127.0)) / 127.0))
+            ampl = int(127 * float(note.get('ampl', float(note.get('vel', 127.0)) / 127.0)))
             time = float(note.get('time'))
             dur = float(note.get('dur'))
             if options.notes: