client.py 7.5 KB

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