Bläddra i källkod

Polyphony ready to test

Grissess 9 år sedan
förälder
incheckning
6dece0e714
2 ändrade filer med 109 tillägg och 102 borttagningar
  1. 50 62
      broadcast.py
  2. 59 40
      client.py

+ 50 - 62
broadcast.py

@@ -36,6 +36,7 @@ parser.add_option('-W', '--wait-time', dest='wait_time', type='float', help='How
 parser.add_option('-B', '--bind-addr', dest='bind_addr', help='The IP address (or IP:port) to bind to (influences the network to send to)')
 parser.add_option('--repeat', dest='repeat', action='store_true', help='Repeat the file playlist indefinitely')
 parser.add_option('-n', '--number', dest='number', type='int', help='Number of clients to use; if negative (default -1), use the product of stream count and the absolute value of this parameter')
+parser.add_option('--dry', dest='dry', action='store_true', help='Dry run--don\'t actually search for or play to clients, but pretend they exist (useful with -G)')
 parser.add_option('-G', '--gui', dest='gui', default='', help='set a GUI to use')
 parser.add_option('--pg-fullscreen', dest='fullscreen', action='store_true', help='Use a full-screen video mode')
 parser.add_option('--pg-width', dest='pg_width', type='int', help='Width of the pygame window')
@@ -103,7 +104,7 @@ def gui_pygame():
         idx = 0
         for cli, note in sorted(playing_notes.items(), key = lambda pair: pair[0]):
             pitch = note[0]
-            col = colorsys.hls_to_rgb(float(idx) / len(clients), note[1]/2.0, 1.0)
+            col = colorsys.hls_to_rgb(float(idx) / len(targets), note[1]/2.0, 1.0)
             col = [int(i*255) for i in col]
             disp.fill(col, (WIDTH - 1, HEIGHT - pitch * PFAC - PFAC, 1, PFAC))
             idx += 1
@@ -134,22 +135,20 @@ if options.bind_addr:
     s.bind((addr, int(port)))
 
 clients = []
+targets = []
 uid_groups = {}
 type_groups = {}
 
-s.sendto(str(Packet(CMD.PING)), ('255.255.255.255', PORT))
-s.settimeout(options.wait_time)
+if not options.dry:
+    s.sendto(str(Packet(CMD.PING)), ('255.255.255.255', PORT))
+    s.settimeout(options.wait_time)
 
-try:
-	while True:
-		data, src = s.recvfrom(4096)
-		clients.append(src)
-except socket.timeout:
-	pass
-
-playing_notes = {}
-for cli in clients:
-    playing_notes[cli] = (0, 0)
+    try:
+            while True:
+                    data, src = s.recvfrom(4096)
+                    clients.append(src)
+    except socket.timeout:
+            pass
 
 print len(clients), 'detected clients'
 
@@ -178,6 +177,12 @@ for cl in clients:
 		s.sendto(str(Packet(CMD.QUIT)), cl)
         if options.silence:
                 s.sendto(str(Packet(CMD.PLAY, 0, 1, 1, 0.0)), cl)
+        for i in xrange(pkt.data[0]):
+            targets.append(cl+(i,))
+
+playing_notes = {}
+for tg in targets:
+    playing_notes[tg] = (0, 0)
 
 if options.gui:
     gui_thr = threading.Thread(target=GUIS[options.gui], args=())
@@ -190,16 +195,16 @@ if options.play:
             options.play[i] = int(val[1:])
         else:
             options.play[i] = int(440.0 * 2**((int(val) - 69)/12.0))
-    for i, cl in enumerate(clients):
-        s.sendto(str(Packet(CMD.PLAY, int(options.duration), int(1000000*(options.duration-int(options.duration))), options.play[i%len(options.play)], options.volume)), cl)
+    for i, cl in enumerate(targets):
+        s.sendto(str(Packet(CMD.PLAY, int(options.duration), int(1000000*(options.duration-int(options.duration))), options.play[i%len(options.play)], options.volume, cl[2])), cl[:2])
     if not options.play_async:
         time.sleep(options.duration)
     exit()
 
 if options.test and options.sync_test:
     time.sleep(0.25)
-    for cl in clients:
-        s.sendto(str(Packet(CMD.PLAY, 0, 250000, 880, 1.0)), cl)
+    for cl in targets:
+        s.sendto(str(Packet(CMD.PLAY, 0, 250000, 880, 1.0, cl[2])), cl[:2])
 
 if options.test or options.quit or options.silence:
     print uid_groups
@@ -208,8 +213,8 @@ if options.test or options.quit or options.silence:
 
 if options.random > 0:
     while True:
-        for cl in clients:
-            s.sendto(str(Packet(CMD.PLAY, int(options.random), int(1000000*(options.random-int(options.random))), random.randint(options.rand_low, options.rand_high), options.volume)), cl)
+        for cl in targets:
+            s.sendto(str(Packet(CMD.PLAY, int(options.random), int(1000000*(options.random-int(options.random))), random.randint(options.rand_low, options.rand_high), options.volume, cl[2])), cl[:2])
         time.sleep(options.random)
 
 if options.live or options.list_live:
@@ -223,7 +228,7 @@ if options.live or options.list_live:
         print sequencer.SequencerHardware()
         exit()
     seq = sequencer.SequencerRead(sequencer_resolution=120)
-    client_set = set(clients)
+    client_set = set(targets)
     active_set = {} # note (pitch) -> [client]
     deferred_set = set() # pitches held due to sustain
     sustain_status = False
@@ -270,7 +275,7 @@ if options.live or options.list_live:
                     print 'WARNING: Out of clients to do note %r; dropped'%(event.pitch,)
                     continue
                 cli = sorted(inactive_set)[0]
-                s.sendto(str(Packet(CMD.PLAY, 65535, 0, int(440.0 * 2**((event.pitch-69)/12.0)), event.velocity / 127.0)), cli)
+                s.sendto(str(Packet(CMD.PLAY, 65535, 0, int(440.0 * 2**((event.pitch-69)/12.0)), event.velocity / 127.0, cli[2])), cli[:2])
                 active_set.setdefault(event.pitch, []).append(cli)
                 playing_notes[cli] = (event.pitch, event.velocity / 127.0)
                 if options.verbose:
@@ -300,7 +305,7 @@ if options.live or options.list_live:
                                 print 'WARNING: Attempted deferred removal of inactive note %r'%(pitch,)
                                 continue
                             for cli in active_set[pitch]:
-                                s.sendto(str(Packet(CMD.PLAY, 0, 1, 1, 0)), cli)
+                                s.sendto(str(Packet(CMD.PLAY, 0, 1, 1, 0, cli[2])), cli[:2])
                                 playing_notes[cli] = (0, 0)
                             del active_set[pitch]
                         deferred_set.clear()
@@ -322,6 +327,7 @@ for fname in args:
     number = (len(notestreams) * abs(options.number) if options.number < 0 else options.number)
     print len(notestreams), 'notestreams'
     print len(clients), 'clients'
+    print len(targets), 'targets'
     print len(groups), 'groups'
     print number, 'clients used (number)'
 
@@ -360,14 +366,14 @@ for fname in args:
                         raise ValueError('Not an exclusivity: %r'%(part[0],))
             return ret
         def Apply(self, cli):
-            return cli in self.map.get(self.value, [])
+            return cli[:2] in self.map.get(self.value, [])
         def __repr__(self):
             return '<Route of %r to %s:%s>'%(self.group, ('U' if self.map is uid_groups else 'T'), self.value)
 
     class RouteSet(object):
         def __init__(self, clis=None):
             if clis is None:
-                clis = clients[:]
+                clis = targets[:]
             self.clients = clis
             self.routes = []
         def Route(self, stream):
@@ -427,33 +433,6 @@ for fname in args:
         for route in routeset.routes:
             print route
 
-    class NSThread(threading.Thread):
-        def drop_missed(self):
-            nsq, cl = self._Thread__args
-            cnt = 0
-            while nsq and float(nsq[0].get('time'))*factor < time.time() - BASETIME:
-                nsq.pop(0)
-                cnt += 1
-            if options.verbose:
-                print self, 'dropped', cnt, 'notes due to miss'
-            self._Thread__args = (nsq, cl)
-        def wait_for(self, t):
-            if t <= 0:
-                return
-            time.sleep(t)
-	def run(self):
-		nsq, cl = self._Thread__args
-		for note in nsq:
-			ttime = float(note.get('time'))
-			pitch = int(note.get('pitch')) + options.transpose
-			vel = int(note.get('vel'))
-			dur = factor*float(note.get('dur'))
-			while time.time() - BASETIME < factor*ttime:
-				self.wait_for(factor*ttime - (time.time() - BASETIME))
-			s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), int(vel*2 * options.volume/255.0))), cl)
-                        if options.verbose:
-                            print (time.time() - BASETIME), cl, ': PLAY', pitch, dur, vel
-			self.wait_for(dur - ((time.time() - BASETIME) - factor*ttime))
     class NSThread(threading.Thread):
             def drop_missed(self):
                 nsq, cl = self._Thread__args
@@ -476,9 +455,12 @@ for fname in args:
                             ampl = float(note.get('ampl', float(note.get('vel', 127.0)) / 127.0))
                             dur = factor*float(note.get('dur'))
                             while time.time() - BASETIME < factor*ttime:
-                                    self.wait_for(factor*ttime - (time.time() - BASETIME))
-                            for cl in cls:
-                                    s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), ampl * options.volume)), cl)
+                                self.wait_for(factor*ttime - (time.time() - BASETIME))
+                            if options.dry:
+                                cl = self.ident  # XXX hack
+                            else:
+                                for cl in cls:
+                                    s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), ampl * options.volume, cl[2])), cl[:2])
                             if options.verbose:
                                 print (time.time() - BASETIME), cl, ': PLAY', pitch, dur, vel
                             playing_notes[cl] = (pitch, ampl)
@@ -488,15 +470,21 @@ for fname in args:
                         print '% 6.5f'%(time.time() - BASETIME,), cl, ': DONE'
 
     threads = {}
-    nscycle = itertools.cycle(notestreams)
-    for idx, ns in zip(xrange(number), nscycle):
-        cli = routeset.Route(ns)
-        if cli:
+    if options.dry:
+        for ns in notestreams:
             nsq = ns.findall('note')
-            if ns in threads:
-                threads[ns]._Thread__args[1].add(cli)
-            else:
-                threads[ns] = NSThread(args=(nsq, set([cli])))
+            threads[ns] = NSThread(args=(nsq, set()))
+        targets = threads.values()  # XXX hack
+    else:
+        nscycle = itertools.cycle(notestreams)
+        for idx, ns in zip(xrange(number), nscycle):
+            cli = routeset.Route(ns)
+            if cli:
+                nsq = ns.findall('note')
+                if ns in threads:
+                    threads[ns]._Thread__args[1].add(cli)
+                else:
+                    threads[ns] = NSThread(args=(nsq, set([cli])))
 
     if options.verbose:
         print 'Playback threads:'

+ 59 - 40
client.py

@@ -24,6 +24,7 @@ parser.add_option('-u', '--uid', dest='uid', default='', help='Set the UID (iden
 parser.add_option('-p', '--port', dest='port', type='int', default=13676, help='Set the port to listen on')
 parser.add_option('-r', '--rate', dest='rate', type='int', default=44100, help='Set the sample rate of the audio device')
 parser.add_option('-V', '--volume', dest='volume', type='float', default=1.0, help='Set the volume factor (>1 distorts, <1 attenuates)')
+parser.add_option('-n', '--streams', dest='streams', type='int', default=1, help='Set the number of streams this client will play back')
 parser.add_option('-G', '--gui', dest='gui', default='', help='set a GUI to use')
 parser.add_option('--pg-fullscreen', dest='fullscreen', action='store_true', help='Use a full-screen video mode')
 parser.add_option('--pg-samp-width', dest='samp_width', type='int', help='Set the width of the sample pane (by default display width / 2)')
@@ -33,22 +34,24 @@ parser.add_option('--pg-height', dest='height', type='int', help='Set the height
 options, args = parser.parse_args()
 
 PORT = options.port
-STREAMS = 1
+STREAMS = options.streams
 IDENT = 'TONE'
 UID = options.uid
 
-LAST_SAMP = 0
+LAST_SAMPS = [0] * STREAMS
 LAST_SAMPLES = []
-FREQ = 0
-PHASE = 0
+FREQS = [0] * STREAMS
+PHASES = [0] * STREAMS
 RATE = options.rate
 FPB = 64
 
 Z_SAMP = '\x00\x00\x00\x00'
 MAX = 0x7fffffff
-AMP = MAX
+AMPS = [MAX] * STREAMS
 MIN = -0x80000000
 
+EXPIRATIONS = [0] * STREAMS
+
 def lin_interp(frm, to, p):
     return p*to + (1-p)*frm
 
@@ -100,18 +103,21 @@ def pygame_notes():
     clock = pygame.time.Clock()
 
     while True:
-        if FREQ > 0:
-            try:
-                pitch = 12 * math.log(FREQ / 440.0, 2) + 69
-            except ValueError:
-                pitch = 0
-        else:
-            pitch = 0
-        col = [int((AMP / MAX) * 255)] * 3
-
         disp.fill((0, 0, 0), (BGR_WIDTH, 0, SAMP_WIDTH, HEIGHT))
         disp.scroll(-1, 0)
-        disp.fill(col, (BGR_WIDTH - 1, HEIGHT - pitch * PFAC - PFAC, 1, PFAC))
+
+        for i in xrange(STREAMS):
+            FREQ = FREQS[i]
+            AMP = AMPS[i]
+            if FREQ > 0:
+                try:
+                    pitch = 12 * math.log(FREQ / 440.0, 2) + 69
+                except ValueError:
+                    pitch = 0
+            else:
+                pitch = 0
+            col = [int((AMP / MAX) * 255)] * 3
+            disp.fill(col, (BGR_WIDTH - 1, HEIGHT - pitch * PFAC - PFAC, 1, PFAC))
 
         sampwin.scroll(-len(LAST_SAMPLES), 0)
         x = max(0, SAMP_WIDTH - len(LAST_SAMPLES))
@@ -272,9 +278,9 @@ if options.generators:
 #generator = square_wave
 generator = eval(options.generator)
 
-def sigalrm(sig, frm):
-    global FREQ
-    FREQ = 0
+#def sigalrm(sig, frm):
+#    global FREQ
+#    FREQ = 0
 
 def lin_seq(frm, to, cnt):
     step = (to-frm)/float(cnt)
@@ -284,33 +290,44 @@ def lin_seq(frm, to, cnt):
         samps[i] = int(lin_interp(frm, to, p))
     return samps
 
-def samps(freq, phase, cnt):
-    global RATE, AMP
+def samps(freq, amp, phase, cnt):
+    global RATE
     samps = [0]*cnt
     for i in xrange(cnt):
-        samps[i] = int(AMP * max(-1, min(1, options.volume*generator((phase + 2 * math.pi * freq * i / RATE) % (2*math.pi)))))
+        samps[i] = int(amp / float(STREAMS) * max(-1, min(1, options.volume*generator((phase + 2 * math.pi * freq * i / RATE) % (2*math.pi)))))
     return samps, (phase + 2 * math.pi * freq * cnt / RATE) % (2*math.pi)
 
 def to_data(samps):
     return struct.pack('i'*len(samps), *samps)
 
-def gen_data(data, frames, time, status):
-    global FREQ, PHASE, Z_SAMP, LAST_SAMP, LAST_SAMPLES
-    if FREQ == 0:
-        PHASE = 0
-        if LAST_SAMP == 0:
-            if options.gui:
-                LAST_SAMPLES.extend([0]*frames)
-            return (Z_SAMP*frames, pyaudio.paContinue)
-        fdata = lin_seq(LAST_SAMP, 0, frames)
-        if options.gui:
-            LAST_SAMPLES.extend(fdata)
-        LAST_SAMP = fdata[-1]
-        return (to_data(fdata), pyaudio.paContinue)
-    fdata, PHASE = samps(FREQ, PHASE, frames)
+def mix(a, b):
+    return [i + j for i, j in zip(a, b)]
+
+def gen_data(data, frames, tm, status):
+    global FREQS, PHASE, Z_SAMP, LAST_SAMP, LAST_SAMPLES
+    fdata = [0] * frames
+    for i in range(STREAMS):
+        FREQ = FREQS[i]
+        LAST_SAMP = LAST_SAMPS[i]
+        AMP = AMPS[i]
+        EXPIRATION = EXPIRATIONS[i]
+        PHASE = PHASES[i]
+        if FREQ != 0:
+            if time.clock() > EXPIRATION:
+                FREQ = 0
+        if FREQ == 0:
+            PHASES[i] = 0
+            if LAST_SAMP != 0:
+                vdata = lin_seq(LAST_SAMP, 0, frames)
+                fdata = mix(fdata, vdata)
+                LAST_SAMPS[i] = vdata[-1]
+        else:
+            vdata, PHASE = samps(FREQ, AMP, PHASE, frames)
+            fdata = mix(fdata, vdata)
+            PHASES[i] = PHASE
+            LAST_SAMPS[i] = vdata[-1]
     if options.gui:
         LAST_SAMPLES.extend(fdata)
-    LAST_SAMP = fdata[-1]
     return (to_data(fdata), pyaudio.paContinue)
 
 pa = pyaudio.PyAudio()
@@ -335,7 +352,7 @@ if options.test:
 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 sock.bind(('', PORT))
 
-signal.signal(signal.SIGALRM, sigalrm)
+#signal.signal(signal.SIGALRM, sigalrm)
 
 while True:
     data = ''
@@ -353,10 +370,12 @@ while True:
     elif pkt.cmd == CMD.QUIT:
         break
     elif pkt.cmd == CMD.PLAY:
+        voice = pkt.data[4]
         dur = pkt.data[0]+pkt.data[1]/1000000.0
-        FREQ = pkt.data[2]
-        AMP = MAX * max(min(pkt.as_float(3), 1.0), 0.0)
-        signal.setitimer(signal.ITIMER_REAL, dur)
+        FREQS[voice] = pkt.data[2]
+        AMPS[voice] = MAX * max(min(pkt.as_float(3), 1.0), 0.0)
+        EXPIRATIONS[voice] = time.clock() + dur
+        #signal.setitimer(signal.ITIMER_REAL, dur)
     elif pkt.cmd == CMD.CAPS:
         data = [0] * 8
         data[0] = STREAMS