client.py 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. # A simple client that generates sine waves via python-pyaudio
  2. import signal
  3. import pyaudio
  4. import sys
  5. import socket
  6. import time
  7. import math
  8. import struct
  9. import socket
  10. import optparse
  11. import array
  12. from packet import Packet, CMD, stoi
  13. parser = optparse.OptionParser()
  14. parser.add_option('-t', '--test', dest='test', action='store_true', help='Play a test sequence (440,<rest>,880,440), then exit')
  15. parser.add_option('-g', '--generator', dest='generator', default='math.sin', help='Set the generator (to a Python expression)')
  16. parser.add_option('--generators', dest='generators', action='store_true', help='Show the list of generators, then exit')
  17. parser.add_option('-u', '--uid', dest='uid', default='', help='Set the UID (identifier) of this client in the network')
  18. parser.add_option('-p', '--port', dest='port', type='int', default=13676, help='Set the port to listen on')
  19. parser.add_option('-r', '--rate', dest='rate', type='int', default=44100, help='Set the sample rate of the audio device')
  20. options, args = parser.parse_args()
  21. PORT = options.port
  22. STREAMS = 1
  23. IDENT = 'TONE'
  24. UID = options.uid
  25. LAST_SAMP = 0
  26. FREQ = 0
  27. PHASE = 0
  28. RATE = options.rate
  29. FPB = 64
  30. Z_SAMP = '\x00\x00\x00\x00'
  31. MAX = 0x7fffffff
  32. AMP = MAX
  33. MIN = -0x80000000
  34. def lin_interp(frm, to, p):
  35. return p*to + (1-p)*frm
  36. # Generator functions--should be cyclic within [0, 2*math.pi) and return [-1, 1]
  37. GENERATORS = [{'name': 'math.sin', 'args': None, 'desc': 'Sine function'},
  38. {'name':'math.cos', 'args': None, 'desc': 'Cosine function'}]
  39. def generator(desc=None, args=None):
  40. def inner(f, desc=desc, args=args):
  41. if desc is None:
  42. desc = f.__doc__
  43. GENERATORS.append({'name': f.__name__, 'desc': desc, 'args': args})
  44. return f
  45. return inner
  46. @generator('Simple triangle wave (peaks/troughs at pi/2, 3pi/2)')
  47. def tri_wave(theta):
  48. if theta < math.pi/2:
  49. return lin_interp(0, 1, theta/(math.pi/2))
  50. elif theta < 3*math.pi/2:
  51. return lin_interp(1, -1, (theta-math.pi/2)/math.pi)
  52. else:
  53. return lin_interp(-1, 0, (theta-3*math.pi/2)/(math.pi/2))
  54. @generator('Simple square wave (piecewise 1 at x<pi, 0 else)')
  55. def square_wave(theta):
  56. if theta < math.pi:
  57. return 1
  58. else:
  59. return -1
  60. @generator('File generator', '(<file>[, <bits=8>[, <signed=True>[, <0=linear interp (default), 1=nearest>[, <swapbytes=False>]]]])')
  61. class file_samp(object):
  62. LINEAR = 0
  63. NEAREST = 1
  64. TYPES = {8: 'B', 16: 'H', 32: 'L'}
  65. def __init__(self, fname, bits=8, signed=True, samp=LINEAR, swab=False):
  66. tp = self.TYPES[bits]
  67. if signed:
  68. tp = tp.lower()
  69. self.max = float((2 << bits) - 1)
  70. self.buffer = array.array(tp)
  71. self.buffer.fromstring(open(fname, 'rb').read())
  72. if swab:
  73. self.buffer.byteswap()
  74. self.samp = samp
  75. def __call__(self, theta):
  76. norm = theta / (2*math.pi)
  77. if self.samp == self.LINEAR:
  78. v = norm*len(self.buffer)
  79. l = int(math.floor(v))
  80. h = int(math.ceil(v))
  81. if l == h:
  82. return self.buffer[l]/self.max
  83. if h >= len(self.buffer):
  84. h = 0
  85. return lin_interp(self.buffer[l], self.buffer[h], v-l)/self.max
  86. elif self.samp == self.NEAREST:
  87. return self.buffer[int(math.ceil(norm*len(self.buffer) - 0.5))]/self.max
  88. @generator('Harmonics generator (adds overtones at f, 2f, 3f, 4f, etc.)', '(<generator>, <amplitude of f>, <amp 2f>, <amp 3f>, ...)')
  89. class harmonic(object):
  90. def __init__(self, gen, *spectrum):
  91. self.gen = gen
  92. self.spectrum = spectrum
  93. def __call__(self, theta):
  94. return max(-1, min(1, sum([amp*self.gen((i+1)*theta % (2*math.pi)) for i, amp in enumerate(self.spectrum)])))
  95. @generator('Mix generator', '(<generator>[, <amp>], [<generator>[, <amp>], [...]])')
  96. class mixer(object):
  97. def __init__(self, *specs):
  98. self.pairs = []
  99. i = 0
  100. while i < len(specs):
  101. if i+1 < len(specs) and isinstance(specs[i+1], (float, int)):
  102. pair = (specs[i], specs[i+1])
  103. i += 2
  104. else:
  105. pair = (specs[i], None)
  106. i += 1
  107. self.pairs.append(pair)
  108. tamp = 1 - min(1, sum([amp for gen, amp in self.pairs if amp is not None]))
  109. parts = float(len([None for gen, amp in self.pairs if amp is None]))
  110. for idx, pair in enumerate(self.pairs):
  111. if pair[1] is None:
  112. self.pairs[idx] = (pair[0], tamp / parts)
  113. def __call__(self, theta):
  114. return max(-1, min(1, sum([amp*gen(theta) for gen, amp in self.pairs])))
  115. @generator('Phase offset generator (in radians; use math.pi)', '(<generator>, <offset>)')
  116. class phase_off(object):
  117. def __init__(self, gen, offset):
  118. self.gen = gen
  119. self.offset = offset
  120. def __call__(self, theta):
  121. return self.gen((theta + self.offset) % (2*math.pi))
  122. if options.generators:
  123. for item in GENERATORS:
  124. print item['name'],
  125. if item['args'] is not None:
  126. print item['args'],
  127. print '--', item['desc']
  128. exit()
  129. #generator = math.sin
  130. #generator = tri_wave
  131. #generator = square_wave
  132. generator = eval(options.generator)
  133. def sigalrm(sig, frm):
  134. global FREQ
  135. FREQ = 0
  136. def lin_seq(frm, to, cnt):
  137. step = (to-frm)/float(cnt)
  138. samps = [0]*cnt
  139. for i in xrange(cnt):
  140. p = i / float(cnt-1)
  141. samps[i] = int(lin_interp(frm, to, p))
  142. return samps
  143. def samps(freq, phase, cnt):
  144. global RATE, AMP
  145. samps = [0]*cnt
  146. for i in xrange(cnt):
  147. samps[i] = int(AMP * generator((phase + 2 * math.pi * freq * i / RATE) % (2*math.pi)))
  148. return samps, (phase + 2 * math.pi * freq * cnt / RATE) % (2*math.pi)
  149. def to_data(samps):
  150. return struct.pack('i'*len(samps), *samps)
  151. def gen_data(data, frames, time, status):
  152. global FREQ, PHASE, Z_SAMP, LAST_SAMP
  153. if FREQ == 0:
  154. PHASE = 0
  155. if LAST_SAMP == 0:
  156. return (Z_SAMP*frames, pyaudio.paContinue)
  157. fdata = lin_seq(LAST_SAMP, 0, frames)
  158. LAST_SAMP = fdata[-1]
  159. return (to_data(fdata), pyaudio.paContinue)
  160. fdata, PHASE = samps(FREQ, PHASE, frames)
  161. LAST_SAMP = fdata[-1]
  162. return (to_data(fdata), pyaudio.paContinue)
  163. pa = pyaudio.PyAudio()
  164. stream = pa.open(rate=RATE, channels=1, format=pyaudio.paInt32, output=True, frames_per_buffer=FPB, stream_callback=gen_data)
  165. if options.test:
  166. FREQ = 440
  167. time.sleep(1)
  168. FREQ = 0
  169. time.sleep(1)
  170. FREQ = 880
  171. time.sleep(1)
  172. FREQ = 440
  173. time.sleep(2)
  174. exit()
  175. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  176. sock.bind(('', PORT))
  177. signal.signal(signal.SIGALRM, sigalrm)
  178. while True:
  179. data = ''
  180. while not data:
  181. try:
  182. data, cli = sock.recvfrom(4096)
  183. except socket.error:
  184. pass
  185. pkt = Packet.FromStr(data)
  186. print 'From', cli, 'command', pkt.cmd
  187. if pkt.cmd == CMD.KA:
  188. pass
  189. elif pkt.cmd == CMD.PING:
  190. sock.sendto(data, cli)
  191. elif pkt.cmd == CMD.QUIT:
  192. break
  193. elif pkt.cmd == CMD.PLAY:
  194. dur = pkt.data[0]+pkt.data[1]/1000000.0
  195. FREQ = pkt.data[2]
  196. AMP = MAX * (pkt.data[3]/255.0)
  197. signal.setitimer(signal.ITIMER_REAL, dur)
  198. elif pkt.cmd == CMD.CAPS:
  199. data = [0] * 8
  200. data[0] = STREAMS
  201. data[1] = stoi(IDENT)
  202. for i in xrange(len(UID)/4):
  203. data[i+2] = stoi(UID[4*i:4*(i+1)])
  204. sock.sendto(str(Packet(CMD.CAPS, *data)), cli)
  205. else:
  206. print 'Unknown cmd', pkt.cmd