broadcast.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  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='int', 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('--help-routes', dest='help_routes', action='store_true', help='Show help about routing directives')
  15. parser.set_defaults(routes=[])
  16. options, args = parser.parse_args()
  17. if options.help_routes:
  18. 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.
  19. Routes are fully specified by:
  20. -The attribute to be routed on (either type "T", or UID "U")
  21. -The value of that attribute
  22. -The exclusivity of that route ("+" for inclusive, "-" for exclusive)
  23. -The stream group to be routed there.
  24. The syntax for that specification resembles the following:
  25. broadcast.py -r U:bass=+bass -r U:treble1,U:treble2=+treble -r T:BEEP=-beeps,-trk3,-trk5
  26. 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.'''
  27. exit()
  28. PORT = 13676
  29. if len(sys.argv) > 2:
  30. factor = float(sys.argv[2])
  31. else:
  32. factor = 1
  33. print 'Factor:', factor
  34. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  35. s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
  36. clients = []
  37. uid_groups = {}
  38. type_groups = {}
  39. s.sendto(str(Packet(CMD.PING)), ('255.255.255.255', PORT))
  40. s.settimeout(0.5)
  41. try:
  42. while True:
  43. data, src = s.recvfrom(4096)
  44. clients.append(src)
  45. except socket.timeout:
  46. pass
  47. print 'Clients:'
  48. for cl in clients:
  49. print cl,
  50. s.sendto(str(Packet(CMD.CAPS)), cl)
  51. data, _ = s.recvfrom(4096)
  52. pkt = Packet.FromStr(data)
  53. print 'ports', pkt.data[0],
  54. tp = itos(pkt.data[1])
  55. print 'type', tp,
  56. uid = ''.join([itos(i) for i in pkt.data[2:]]).rstrip('\x00')
  57. print 'uid', uid
  58. if uid == '':
  59. uid = None
  60. uid_groups.setdefault(uid, []).append(cl)
  61. type_groups.setdefault(tp, []).append(cl)
  62. if options.test:
  63. s.sendto(str(Packet(CMD.PLAY, 0, 250000, 440, 255)), cl)
  64. time.sleep(0.25)
  65. s.sendto(str(Packet(CMD.PLAY, 0, 250000, 880, 255)), cl)
  66. if options.quit:
  67. s.sendto(str(Packet(CMD.QUIT)), cl)
  68. if options.test or options.quit:
  69. print uid_groups
  70. print type_groups
  71. exit()
  72. try:
  73. iv = ET.parse(sys.argv[1]).getroot()
  74. except IOError:
  75. print 'Bad file'
  76. exit()
  77. notestreams = iv.findall("./streams/stream[@type='ns']")
  78. groups = set([ns.get('group') for ns in notestreams if 'group' in ns.keys()])
  79. print len(notestreams), 'notestreams'
  80. print len(clients), 'clients'
  81. print len(groups), 'groups'
  82. class NSThread(threading.Thread):
  83. def run(self):
  84. nsq, cl = self._Thread__args
  85. for note in nsq:
  86. ttime = float(note.get('time'))
  87. pitch = int(note.get('pitch'))
  88. vel = int(note.get('vel'))
  89. dur = factor*float(note.get('dur'))
  90. while time.time() - BASETIME < factor*ttime:
  91. time.sleep(factor*ttime - (time.time() - BASETIME))
  92. s.sendto(str(Packet(CMD.PLAY, int(dur), int((dur*1000000)%1000000), int(440.0 * 2**((pitch-69)/12.0)), vel*2)), cl)
  93. time.sleep(dur)
  94. threads = []
  95. for ns in notestreams:
  96. if not clients:
  97. print 'WARNING: Out of clients!',
  98. break
  99. nsq = ns.findall('note')
  100. threads.append(NSThread(args=(nsq, clients.pop(0))))
  101. BASETIME = time.time()
  102. for thr in threads:
  103. thr.start()
  104. for thr in threads:
  105. thr.join()