client.py 7.7 KB

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