diff options
| -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 | 
