broadcast.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import socket
  2. import sys
  3. import struct
  4. import time
  5. import xml.etree.ElementTree as ET
  6. import threading
  7. import optparse
  8. from packet import Packet, CMD, itos
  9. parser = optparse.OptionParser()
  10. parser.add_option('-t', '--test', dest='test', action='store_true', help='Play a test tone (440, 880) on all clients in sequence (the last overlaps with the first of the next)')
  11. parser.add_option('-q', '--quit', dest='quit', action='store_true', help='Instruct all clients to quit')
  12. parser.add_option('-f', '--factor', dest='factor', type='float', default=1.0, help='Rescale time by this factor (0<f<1 are faster; 0.5 is twice the speed, 2 is half)')
  13. parser.add_option('-r', '--route', dest='routes', action='append', help='Add a routing directive (see --route-help)')
  14. parser.add_option('-v', '--verbose', dest='verbose', action='store_true', help='Be verbose; dump events and actual time (can slow down performance!)')
  15. parser.add_option('--help-routes', dest='help_routes', action='store_true', help='Show help about routing directives')
  16. parser.set_defaults(routes=[])
  17. options, args = parser.parse_args()
  18. if options.help_routes:
  19. print '''Routes are a way of either exclusively or mutually binding certain streams to certain playback clients. They are especially fitting in heterogenous environments where some clients will outperform others in certain pitches or with certain parts.
  20. Routes are fully specified by:
  21. -The attribute to be routed on (either type "T", or UID "U")
  22. -The value of that attribute
  23. -The exclusivity of that route ("+" for inclusive, "-" for exclusive)
  24. -The stream group to be routed there.
  25. The syntax for that specification resembles the following:
  26. broadcast.py -r U:bass=+bass -r U:treble1,U:treble2=+treble -r T:BEEP=-beeps,-trk3,-trk5
  27. The specifier consists of a comma-separated list of attribute-colon-value pairs, followed by an equal sign. After this is a comma-separated list of exclusivities paired with the name of a stream group as specified in the file. The above example shows that stream groups "bass", "treble", and "beeps" will be routed to clients with UID "bass", "treble", and TYPE "BEEP" respectively. Additionally, TYPE "BEEP" will receive tracks 4 and 6 (indices 3 and 5) of the MIDI file (presumably split with -T), and that these three groups are exclusively to be routed to TYPE "BEEP" clients only (the broadcaster will drop the stream if no more are available), as opposed to the preference of the bass and treble groups, which may be routed onto other stream clients if they are available.'''
  28. exit()
  29. PORT = 13676
  30. factor = options.factor
  31. print 'Factor:', factor
  32. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  33. s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  34. clients = []
  35. uid_groups = {}
  36. type_groups = {}
  37. s.sendto(str(Packet(CMD.PING)), ('255.255.255.255', PORT))
  38. s.settimeout(0.5)
  39. try:
  40. while True:
  41. data, src = s.recvfrom(4096)
  42. clients.append(src)
  43. except socket.timeout:
  44. pass
  45. print 'Clients:'
  46. for cl in clients:
  47. print cl,
  48. s.sendto(str(Packet(CMD.CAPS)), cl)
  49. data, _ = s.recvfrom(4096)
  50. pkt = Packet.FromStr(data)
  51. print 'ports', pkt.data[0],
  52. tp = itos(pkt.data[1])
  53. print 'type', tp,
  54. uid = ''.join([itos(i) for i in pkt.data[2:]]).rstrip('\x00')
  55. print 'uid', uid
  56. if uid == '':
  57. uid = None
  58. uid_groups.setdefault(uid, []).append(cl)
  59. type_groups.setdefault(tp, []).append(cl)
  60. if options.test:
  61. s.sendto(str(Packet(CMD.PLAY, 0, 250000, 440, 255)), cl)
  62. time.sleep(0.25)
  63. s.sendto(str(Packet(CMD.PLAY, 0, 250000, 880, 255)), cl)
  64. if options.quit:
  65. s.sendto(str(Packet(CMD.QUIT)), cl)
  66. if options.test or options.quit:
  67. print uid_groups
  68. print type_groups
  69. exit()
  70. try:
  71. iv = ET.parse(args[0]).getroot()
  72. except IOError:
  73. print 'Bad file'
  74. exit()
  75. notestreams = iv.findall("./streams/stream[@type='ns']")
  76. groups = set([ns.get('group') for ns in notestreams if 'group' in ns.keys()])
  77. print len(notestreams), 'notestreams'
  78. print len(clients), 'clients'
  79. print len(groups), 'groups'
  80. class NSThread(threading.Thread):
  81. def wait_for(self, t):
  82. if t <= 0:
  83. return
  84. time.sleep(t)
  85. def run(self):
  86. nsq, cl = self._Thread__args
  87. for note in nsq:
  88. ttime = float(note.get('time'))
  89. pitch = int(note.get('pitch'))
  90. vel = int(note.get('vel'))
  91. dur = factor*float(note.get('dur'))
  92. while time.time() - BASETIME < factor*ttime:
  93. self.wait_for(factor*ttime - (time.time() - BASETIME))
  94. s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), vel*2)), cl)
  95. print (time.time() - BASETIME), cl, ': PLAY', pitch, dur, vel
  96. self.wait_for(dur - ((time.time() - BASETIME) - factor*ttime))
  97. print '% 6.5f'%(time.time() - BASETIME,), cl, ': DONE'
  98. threads = []
  99. for ns in notestreams:
  100. if not clients:
  101. print 'WARNING: Out of clients!',
  102. break
  103. nsq = ns.findall('note')
  104. threads.append(NSThread(args=(nsq, clients.pop(0))))
  105. BASETIME = time.time()
  106. for thr in threads:
  107. thr.start()
  108. for thr in threads:
  109. thr.join()