diff options
author | Grissess <grissess@nexusg.org> | 2016-06-14 10:22:56 -0400 |
---|---|---|
committer | Grissess <grissess@nexusg.org> | 2016-06-14 10:22:56 -0400 |
commit | 6dece0e714544e63fac5f08b2d3b7dcc50092321 (patch) | |
tree | 4cc552c54c6b2274cce566021a4349255b158bce | |
parent | 8028934567b4c4a4046aa4665e26c7c59b96a6c3 (diff) |
Polyphony ready to test
-rw-r--r-- | broadcast.py | 112 | ||||
-rw-r--r-- | client.py | 99 |
2 files changed, 109 insertions, 102 deletions
diff --git a/broadcast.py b/broadcast.py index 91b4e71..7d993e6 100644 --- a/broadcast.py +++ b/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): @@ -428,33 +434,6 @@ for fname in args: 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 cnt = 0 @@ -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:' @@ -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 |