client.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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. import threading
  14. import thread
  15. import colorsys
  16. from packet import Packet, CMD, PLF, stoi
  17. parser = optparse.OptionParser()
  18. parser.add_option('-t', '--test', dest='test', action='store_true', help='Play a test sequence (440,<rest>,880,440), then exit')
  19. parser.add_option('-g', '--generator', dest='generator', default='math.sin', help='Set the generator (to a Python expression)')
  20. parser.add_option('--generators', dest='generators', action='store_true', help='Show the list of generators, then exit')
  21. parser.add_option('-u', '--uid', dest='uid', default='', help='Set the UID (identifier) of this client in the network')
  22. parser.add_option('-p', '--port', dest='port', type='int', default=13676, help='Set the port to listen on')
  23. parser.add_option('-r', '--rate', dest='rate', type='int', default=44100, help='Set the sample rate of the audio device')
  24. parser.add_option('-V', '--volume', dest='volume', type='float', default=1.0, help='Set the volume factor (>1 distorts, <1 attenuates)')
  25. parser.add_option('-n', '--streams', dest='streams', type='int', default=1, help='Set the number of streams this client will play back')
  26. parser.add_option('-N', '--numpy', dest='numpy', action='store_true', help='Use numpy acceleration')
  27. parser.add_option('-G', '--gui', dest='gui', default='', help='set a GUI to use')
  28. parser.add_option('-c', '--clamp', dest='clamp', action='store_true', help='Clamp over-the-wire amplitudes to 0.0-1.0')
  29. parser.add_option('-C', '--chorus', dest='chorus', default=0.0, type='float', help='Apply uniform random offsets (in MIDI pitch space)')
  30. parser.add_option('--vibrato', dest='vibrato', default=0.0, type='float', help='Apply periodic perturbances in pitch space by this amplitude (in MIDI pitches)')
  31. parser.add_option('--vibrato-freq', dest='vibrato_freq', default=6.0, type='float', help='Frequency of the vibrato perturbances in Hz')
  32. parser.add_option('--pg-fullscreen', dest='fullscreen', action='store_true', help='Use a full-screen video mode')
  33. parser.add_option('--pg-samp-width', dest='samp_width', type='int', help='Set the width of the sample pane (by default display width / 2)')
  34. parser.add_option('--pg-bgr-width', dest='bgr_width', type='int', help='Set the width of the bargraph pane (by default display width / 2)')
  35. parser.add_option('--pg-height', dest='height', type='int', help='Set the height of the window or full-screen video mode')
  36. parser.add_option('--pg-no-colback', dest='no_colback', action='store_true', help='Don\'t render a colored background')
  37. parser.add_option('--pg-low-freq', dest='low_freq', type='int', default=40, help='Low frequency for colored background')
  38. parser.add_option('--pg-high-freq', dest='high_freq', type='int', default=1500, help='High frequency for colored background')
  39. parser.add_option('--pg-log-base', dest='log_base', type='int', default=2, help='Logarithmic base for coloring (0 to make linear)')
  40. parser.add_option('--counter-modulus', dest='counter_modulus', type='int', default=16, help='Number of packet events in period of the terminal color scroll on the left margin')
  41. parser.add_option('--pcm-corr-rate', dest='pcm_corr_rate', type='float', default=0.05, help='Amount of time to correct buffer drift, measured as percentage of the current sync rate')
  42. options, args = parser.parse_args()
  43. if options.numpy:
  44. import numpy
  45. PORT = options.port
  46. STREAMS = options.streams
  47. IDENT = 'TONE'
  48. UID = options.uid
  49. LAST_SAMPS = [0] * STREAMS
  50. LAST_SAMPLES = []
  51. FREQS = [0] * STREAMS
  52. REAL_FREQS = [0] * STREAMS
  53. PHASES = [0] * STREAMS
  54. RATE = options.rate
  55. FPB = 64
  56. Z_SAMP = '\x00\x00\x00\x00'
  57. MAX = 0x7fffffff
  58. AMPS = [MAX] * STREAMS
  59. MIN = -0x80000000
  60. EXPIRATIONS = [0] * STREAMS
  61. QUEUED_PCM = ''
  62. DRIFT_FACTOR = 1.0
  63. DRIFT_ERROR = 0.0
  64. LAST_SYN = None
  65. def lin_interp(frm, to, p):
  66. return p*to + (1-p)*frm
  67. def rgb_for_freq_amp(f, a):
  68. a = max((min((a, 1.0)), 0.0))
  69. pitchval = float(f - options.low_freq) / (options.high_freq - options.low_freq)
  70. if options.log_base == 0:
  71. try:
  72. pitchval = math.log(pitchval) / math.log(options.log_base)
  73. except ValueError:
  74. pass
  75. bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * (a ** 2), 1.0)
  76. return [int(i*255) for i in bgcol]
  77. # GUIs
  78. GUIs = {}
  79. def GUI(f):
  80. GUIs[f.__name__] = f
  81. return f
  82. @GUI
  83. def pygame_notes():
  84. import pygame
  85. import pygame.gfxdraw
  86. pygame.init()
  87. dispinfo = pygame.display.Info()
  88. DISP_WIDTH = 640
  89. DISP_HEIGHT = 480
  90. if dispinfo.current_h > 0 and dispinfo.current_w > 0:
  91. DISP_WIDTH = dispinfo.current_w
  92. DISP_HEIGHT = dispinfo.current_h
  93. SAMP_WIDTH = DISP_WIDTH / 2
  94. if options.samp_width > 0:
  95. SAMP_WIDTH = options.samp_width
  96. BGR_WIDTH = DISP_WIDTH / 2
  97. if options.bgr_width > 0:
  98. BGR_WIDTH = options.bgr_width
  99. HEIGHT = DISP_HEIGHT
  100. if options.height > 0:
  101. HEIGHT = options.height
  102. flags = 0
  103. if options.fullscreen:
  104. flags |= pygame.FULLSCREEN
  105. disp = pygame.display.set_mode((SAMP_WIDTH + BGR_WIDTH, HEIGHT), flags)
  106. WIDTH, HEIGHT = disp.get_size()
  107. SAMP_WIDTH = WIDTH / 2
  108. BGR_WIDTH = WIDTH - SAMP_WIDTH
  109. PFAC = HEIGHT / 128.0
  110. sampwin = pygame.Surface((SAMP_WIDTH, HEIGHT))
  111. sampwin.set_colorkey((0, 0, 0))
  112. lastsy = HEIGHT / 2
  113. bgrwin = pygame.Surface((BGR_WIDTH, HEIGHT))
  114. bgrwin.set_colorkey((0, 0, 0))
  115. clock = pygame.time.Clock()
  116. font = pygame.font.SysFont(pygame.font.get_default_font(), 24)
  117. while True:
  118. if options.no_colback:
  119. disp.fill((0, 0, 0), (0, 0, WIDTH, HEIGHT))
  120. else:
  121. gap = WIDTH / STREAMS
  122. for i in xrange(STREAMS):
  123. FREQ = REAL_FREQS[i]
  124. AMP = AMPS[i]
  125. if FREQ > 0:
  126. bgcol = rgb_for_freq_amp(FREQ, float(AMP) / MAX)
  127. else:
  128. bgcol = (0, 0, 0)
  129. #print i, ':', pitchval
  130. disp.fill(bgcol, (i*gap, 0, gap, HEIGHT))
  131. bgrwin.scroll(-1, 0)
  132. bgrwin.fill((0, 0, 0), (BGR_WIDTH - 1, 0, 1, HEIGHT))
  133. for i in xrange(STREAMS):
  134. FREQ = REAL_FREQS[i]
  135. AMP = AMPS[i]
  136. if FREQ > 0:
  137. try:
  138. pitch = 12 * math.log(FREQ / 440.0, 2) + 69
  139. except ValueError:
  140. pitch = 0
  141. else:
  142. pitch = 0
  143. col = [min(max(int((AMP / MAX) * 255), 0), 255)] * 3
  144. bgrwin.fill(col, (BGR_WIDTH - 1, HEIGHT - pitch * PFAC - PFAC, 1, PFAC))
  145. sampwin.scroll(-len(LAST_SAMPLES), 0)
  146. x = max(0, SAMP_WIDTH - len(LAST_SAMPLES))
  147. sampwin.fill((0, 0, 0), (x, 0, SAMP_WIDTH - x, HEIGHT))
  148. for i in LAST_SAMPLES:
  149. sy = int((float(i) / MAX) * (HEIGHT / 2) + (HEIGHT / 2))
  150. pygame.gfxdraw.line(sampwin, x - 1, lastsy, x, sy, (0, 255, 0))
  151. x += 1
  152. lastsy = sy
  153. del LAST_SAMPLES[:]
  154. #w, h = SAMP_WIDTH, HEIGHT
  155. #pts = [(BGR_WIDTH, HEIGHT / 2), (w + BGR_WIDTH, HEIGHT / 2)]
  156. #x = w + BGR_WIDTH
  157. #for i in reversed(LAST_SAMPLES):
  158. # pts.insert(1, (x, int((h / 2) + (float(i) / MAX) * (h / 2))))
  159. # x -= 1
  160. # if x < BGR_WIDTH:
  161. # break
  162. #if len(pts) > 2:
  163. # pygame.gfxdraw.aapolygon(disp, pts, [0, 255, 0])
  164. disp.blit(bgrwin, (0, 0))
  165. disp.blit(sampwin, (BGR_WIDTH, 0))
  166. if QUEUED_PCM:
  167. tsurf = font.render('%+011.6g'%(DRIFT_FACTOR - 1,), True, (255, 255, 255), (0, 0, 0))
  168. disp.fill((0, 0, 0), tsurf.get_rect())
  169. disp.blit(tsurf, (0, 0))
  170. pygame.display.flip()
  171. for ev in pygame.event.get():
  172. if ev.type == pygame.KEYDOWN:
  173. if ev.key == pygame.K_ESCAPE:
  174. thread.interrupt_main()
  175. pygame.quit()
  176. exit()
  177. elif ev.type == pygame.QUIT:
  178. thread.interrupt_main()
  179. pygame.quit()
  180. exit()
  181. clock.tick(60)
  182. # Generator functions--should be cyclic within [0, 2*math.pi) and return [-1, 1]
  183. GENERATORS = [{'name': 'math.sin', 'args': None, 'desc': 'Sine function'},
  184. {'name':'math.cos', 'args': None, 'desc': 'Cosine function'}]
  185. def generator(desc=None, args=None):
  186. def inner(f, desc=desc, args=args):
  187. if desc is None:
  188. desc = f.__doc__
  189. GENERATORS.append({'name': f.__name__, 'desc': desc, 'args': args})
  190. return f
  191. return inner
  192. @generator('Simple triangle wave (peaks/troughs at pi/2, 3pi/2)')
  193. def tri_wave(theta):
  194. if theta < math.pi/2:
  195. return lin_interp(0, 1, theta/(math.pi/2))
  196. elif theta < 3*math.pi/2:
  197. return lin_interp(1, -1, (theta-math.pi/2)/math.pi)
  198. else:
  199. return lin_interp(-1, 0, (theta-3*math.pi/2)/(math.pi/2))
  200. @generator('Saw wave (line from (0, 1) to (2pi, -1))')
  201. def saw_wave(theta):
  202. return lin_interp(1, -1, theta/(math.pi * 2))
  203. @generator('Simple square wave (piecewise 1 at x<pi, 0 else)')
  204. def square_wave(theta):
  205. if theta < math.pi:
  206. return 1
  207. else:
  208. return -1
  209. @generator('Random (noise) generator')
  210. def noise(theta):
  211. return random.random() * 2 - 1
  212. @generator('File generator', '(<file>[, <bits=8>[, <signed=True>[, <0=linear interp (default), 1=nearest>[, <swapbytes=False>]]]])')
  213. class file_samp(object):
  214. LINEAR = 0
  215. NEAREST = 1
  216. TYPES = {8: 'B', 16: 'H', 32: 'L'}
  217. def __init__(self, fname, bits=8, signed=True, samp=LINEAR, swab=False):
  218. tp = self.TYPES[bits]
  219. if signed:
  220. tp = tp.lower()
  221. self.max = float((2 << bits) - 1)
  222. self.buffer = array.array(tp)
  223. self.buffer.fromstring(open(fname, 'rb').read())
  224. if swab:
  225. self.buffer.byteswap()
  226. self.samp = samp
  227. def __call__(self, theta):
  228. norm = theta / (2*math.pi)
  229. if self.samp == self.LINEAR:
  230. v = norm*len(self.buffer)
  231. l = int(math.floor(v))
  232. h = int(math.ceil(v))
  233. if l == h:
  234. return self.buffer[l]/self.max
  235. if h >= len(self.buffer):
  236. h = 0
  237. return lin_interp(self.buffer[l], self.buffer[h], v-l)/self.max
  238. elif self.samp == self.NEAREST:
  239. return self.buffer[int(math.ceil(norm*len(self.buffer) - 0.5))]/self.max
  240. @generator('Harmonics generator (adds overtones at f, 2f, 3f, 4f, etc.)', '(<generator>, <amplitude of f>, <amp 2f>, <amp 3f>, ...)')
  241. class harmonic(object):
  242. def __init__(self, gen, *spectrum):
  243. self.gen = gen
  244. self.spectrum = spectrum
  245. def __call__(self, theta):
  246. return max(-1, min(1, sum([amp*self.gen((i+1)*theta % (2*math.pi)) for i, amp in enumerate(self.spectrum)])))
  247. @generator('General harmonics generator (adds arbitrary overtones)', '(<generator>, <factor of f>, <amplitude>, <factor>, <amplitude>, ...)')
  248. class genharmonic(object):
  249. def __init__(self, gen, *harmonics):
  250. self.gen = gen
  251. self.harmonics = zip(harmonics[::2], harmonics[1::2])
  252. def __call__(self, theta):
  253. return max(-1, min(1, sum([amp * self.gen(i * theta % (2*math.pi)) for i, amp in self.harmonics])))
  254. @generator('Mix generator', '(<generator>[, <amp>], [<generator>[, <amp>], [...]])')
  255. class mixer(object):
  256. def __init__(self, *specs):
  257. self.pairs = []
  258. i = 0
  259. while i < len(specs):
  260. if i+1 < len(specs) and isinstance(specs[i+1], (float, int)):
  261. pair = (specs[i], specs[i+1])
  262. i += 2
  263. else:
  264. pair = (specs[i], None)
  265. i += 1
  266. self.pairs.append(pair)
  267. tamp = 1 - min(1, sum([amp for gen, amp in self.pairs if amp is not None]))
  268. parts = float(len([None for gen, amp in self.pairs if amp is None]))
  269. for idx, pair in enumerate(self.pairs):
  270. if pair[1] is None:
  271. self.pairs[idx] = (pair[0], tamp / parts)
  272. def __call__(self, theta):
  273. return max(-1, min(1, sum([amp*gen(theta) for gen, amp in self.pairs])))
  274. @generator('Phase offset generator (in radians; use math.pi)', '(<generator>, <offset>)')
  275. class phase_off(object):
  276. def __init__(self, gen, offset):
  277. self.gen = gen
  278. self.offset = offset
  279. def __call__(self, theta):
  280. return self.gen((theta + self.offset) % (2*math.pi))
  281. @generator('Normally distributed random-inversion square waves (chorus effect)', '(<sigma>)')
  282. class nd_square_wave(object):
  283. def __init__(self, sig):
  284. self.sig = sig
  285. self.invt = 0
  286. self.lastp = 2*math.pi
  287. def __call__(self, theta):
  288. if theta < self.lastp:
  289. self.invt = random.normalvariate(math.pi, self.sig)
  290. self.lastp = theta
  291. return -1 if theta < self.invt else 1
  292. @generator('Normally distributed random-point triangle waves (chorus effect)', '(<sigma>)')
  293. class nd_tri_wave(object):
  294. def __init__(self, sig):
  295. self.sig = sig
  296. self.p1 = 0.5*math.pi
  297. self.p2 = 1.5*math.pi
  298. self.lastp = 2*math.pi
  299. def __call__(self, theta):
  300. if theta < self.lastp:
  301. self.p1 = random.normalvariate(0.5*math.pi, self.sig)
  302. self.p2 = random.normalvariate(1.5*math.pi, self.sig)
  303. self.lastp = theta
  304. if theta < self.p1:
  305. return lin_interp(0, 1, theta / self.p1)
  306. elif theta < self.p2:
  307. return lin_interp(1, -1, (theta - self.p1) / (self.p2 - self.p1))
  308. else:
  309. return lin_interp(-1, 0, (theta - self.p2) / (2*math.pi - self.p2))
  310. @generator('Random phase offset', '(<generator>, <noise factor>)')
  311. class rand_phase_off(object):
  312. def __init__(self, gen, fac):
  313. self.gen = gen
  314. self.fac = fac
  315. def __call__(self, theta):
  316. return self.gen((theta + self.fac * random.random()) % (2*math.pi))
  317. @generator('Infinite Impulse Response low pass filter', '(<generator>, <RC normalized to dt=1 sample>)')
  318. class lowpass(object):
  319. def __init__(self, gen, rc):
  320. self.gen = gen
  321. self.alpha = 1.0 / (rc + 1)
  322. self.last = 0
  323. def __call__(self, theta):
  324. self.last += self.alpha * (self.gen(theta) - self.last)
  325. return self.last
  326. @generator('Infinite Impulse Response high pass filter', '(<generator>, <RC normalized to dt=1 sample>)')
  327. class highpass(object):
  328. def __init__(self, gen, rc):
  329. self.gen = gen
  330. self.alpha = rc / (rc + 1.0)
  331. self.last = 0
  332. self.lastx = 0
  333. def __call__(self, theta):
  334. x = self.gen(theta)
  335. self.last = self.alpha * (self.last + x - self.lastx)
  336. self.lastx = x
  337. return self.last
  338. @generator('Applies a function to itself repeatedly; often used with filters', '(<times>, <func>, <inner>, <extra arg 1>, <extra arg 2>, ...)')
  339. def order(n, f, i, *args):
  340. cur = f(i, *args)
  341. while n > 0:
  342. cur = f(cur, *args)
  343. n -= 1
  344. return cur
  345. if options.generators:
  346. for item in GENERATORS:
  347. print item['name'],
  348. if item['args'] is not None:
  349. print item['args'],
  350. print '--', item['desc']
  351. exit()
  352. #generator = math.sin
  353. #generator = tri_wave
  354. #generator = square_wave
  355. generator = eval(options.generator)
  356. #def sigalrm(sig, frm):
  357. # global FREQ
  358. # FREQ = 0
  359. if options.numpy:
  360. def lin_seq(frm, to, cnt):
  361. return numpy.linspace(frm, to, cnt, dtype=numpy.int32)
  362. def samps(freq, amp, phase, cnt):
  363. samps = numpy.ndarray((cnt,), numpy.int32)
  364. pvel = 2 * math.pi * freq / RATE
  365. fac = options.volume * amp / float(STREAMS)
  366. for i in xrange(cnt):
  367. samps[i] = fac * max(-1, min(1, generator(phase)))
  368. phase = (phase + pvel) % (2 * math.pi)
  369. return samps, phase
  370. def to_data(samps):
  371. return samps.tobytes()
  372. def mix(a, b):
  373. return a + b
  374. def resample(samps, amt):
  375. samps = numpy.frombuffer(samps, numpy.int32)
  376. return numpy.interp(numpy.linspace(0, samps.shape[0], amt, False), numpy.linspace(0, samps.shape[0], samps.shape[0], False), samps).astype(numpy.int32).tobytes()
  377. else:
  378. def lin_seq(frm, to, cnt):
  379. step = (to-frm)/float(cnt)
  380. samps = [0]*cnt
  381. for i in xrange(cnt):
  382. p = i / float(cnt-1)
  383. samps[i] = int(lin_interp(frm, to, p))
  384. return samps
  385. def samps(freq, amp, phase, cnt):
  386. global RATE
  387. samps = [0]*cnt
  388. for i in xrange(cnt):
  389. samps[i] = int(2*amp / float(STREAMS) * max(-1, min(1, options.volume*generator((phase + 2 * math.pi * freq * i / RATE) % (2*math.pi)))))
  390. return samps, (phase + 2 * math.pi * freq * cnt / RATE) % (2*math.pi)
  391. def to_data(samps):
  392. return struct.pack('i'*len(samps), *samps)
  393. def mix(a, b):
  394. return [min(MAX, max(MIN, i + j)) for i, j in zip(a, b)]
  395. def resample(samps, amt):
  396. isl = len(samps) / 4
  397. if isl == amt:
  398. return samps
  399. arr = struct.unpack(str(isl)+'i', samps)
  400. out = []
  401. for i in range(amt):
  402. effidx = i * (isl / amt)
  403. ieffidx = int(effidx)
  404. if ieffidx == effidx:
  405. out.append(arr[ieffidx])
  406. else:
  407. frac = effidx - ieffidx
  408. out.append(arr[ieffidx] * (1-frac) + arr[ieffidx+1] * frac)
  409. return struct.pack(str(amt)+'i', *out)
  410. def gen_data(data, frames, tm, status):
  411. global FREQS, PHASE, Z_SAMP, LAST_SAMP, LAST_SAMPLES, QUEUED_PCM, DRIFT_FACTOR, DRIFT_ERROR
  412. if len(QUEUED_PCM) >= frames*4:
  413. desired_frames = DRIFT_FACTOR * frames
  414. err_frames = desired_frames - int(desired_frames)
  415. desired_frames = int(desired_frames)
  416. DRIFT_ERROR += err_frames
  417. if DRIFT_ERROR >= 1.0:
  418. desired_frames += 1
  419. DRIFT_ERROR -= 1.0
  420. fdata = QUEUED_PCM[:desired_frames*4]
  421. QUEUED_PCM = QUEUED_PCM[desired_frames*4:]
  422. if options.gui:
  423. LAST_SAMPLES.extend(struct.unpack(str(desired_frames)+'i', fdata))
  424. return resample(fdata, frames), pyaudio.paContinue
  425. if options.numpy:
  426. fdata = numpy.zeros((frames,), numpy.int32)
  427. else:
  428. fdata = [0] * frames
  429. for i in range(STREAMS):
  430. FREQ = FREQS[i]
  431. if options.vibrato > 0 and FREQ > 0:
  432. midi = 12 * math.log(FREQ / 440.0, 2) + 69
  433. midi += options.vibrato * math.sin(time.time() * 2 * math.pi * options.vibrato_freq + i * 2 * math.pi / STREAMS)
  434. FREQ = 440.0 * 2 ** ((midi - 69) / 12)
  435. REAL_FREQS[i] = FREQ
  436. LAST_SAMP = LAST_SAMPS[i]
  437. AMP = AMPS[i]
  438. EXPIRATION = EXPIRATIONS[i]
  439. PHASE = PHASES[i]
  440. if FREQ != 0:
  441. if time.time() > EXPIRATION:
  442. FREQ = 0
  443. FREQS[i] = 0
  444. if FREQ == 0:
  445. if LAST_SAMP != 0:
  446. vdata = lin_seq(LAST_SAMP, 0, frames)
  447. fdata = mix(fdata, vdata)
  448. LAST_SAMPS[i] = vdata[-1]
  449. else:
  450. vdata, PHASE = samps(FREQ, AMP, PHASE, frames)
  451. fdata = mix(fdata, vdata)
  452. PHASES[i] = PHASE
  453. LAST_SAMPS[i] = vdata[-1]
  454. if options.gui:
  455. LAST_SAMPLES.extend(fdata)
  456. return (to_data(fdata), pyaudio.paContinue)
  457. pa = pyaudio.PyAudio()
  458. stream = pa.open(rate=RATE, channels=1, format=pyaudio.paInt32, output=True, frames_per_buffer=FPB, stream_callback=gen_data)
  459. if options.gui:
  460. guithread = threading.Thread(target=GUIs[options.gui])
  461. guithread.setDaemon(True)
  462. guithread.start()
  463. if options.test:
  464. FREQS[0] = 440
  465. EXPIRATIONS[0] = time.time() + 1
  466. time.sleep(1)
  467. FREQS[0] = 0
  468. time.sleep(1)
  469. FREQS[0] = 880
  470. EXPIRATIONS[0] = time.time() + 1
  471. time.sleep(1)
  472. FREQS[0] = 440
  473. EXPIRATIONS[0] = time.time() + 2
  474. time.sleep(2)
  475. exit()
  476. sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  477. sock.bind(('', PORT))
  478. #signal.signal(signal.SIGALRM, sigalrm)
  479. counter = 0
  480. while True:
  481. data = ''
  482. while not data:
  483. try:
  484. data, cli = sock.recvfrom(4096)
  485. except socket.error:
  486. pass
  487. pkt = Packet.FromStr(data)
  488. if pkt.cmd != CMD.PCM:
  489. crgb = [int(i*255) for i in colorsys.hls_to_rgb((float(counter) / options.counter_modulus) % 1.0, 0.5, 1.0)]
  490. print '\x1b[38;2;{};{};{}m#'.format(*crgb),
  491. counter += 1
  492. print '\x1b[mFrom', cli, 'command', pkt.cmd,
  493. if pkt.cmd == CMD.KA:
  494. print '\x1b[37mKA'
  495. elif pkt.cmd == CMD.PING:
  496. sock.sendto(data, cli)
  497. print '\x1b[1;33mPING'
  498. elif pkt.cmd == CMD.QUIT:
  499. print '\x1b[1;31mQUIT'
  500. break
  501. elif pkt.cmd == CMD.PLAY:
  502. voice = pkt.data[4]
  503. dur = pkt.data[0]+pkt.data[1]/1000000.0
  504. freq = pkt.data[2]
  505. if options.chorus > 0:
  506. midi = 12 * math.log(freq / 440.0, 2) + 69
  507. midi += (random.random() * 2 - 1) * options.chorus
  508. freq = 440.0 * 2 ** ((midi - 69) / 12)
  509. FREQS[voice] = freq
  510. amp = pkt.as_float(3)
  511. if options.clamp:
  512. amp = max(min(amp, 1.0), 0.0)
  513. AMPS[voice] = MAX * amp
  514. EXPIRATIONS[voice] = time.time() + dur
  515. if not (pkt.data[5] & PLF.SAMEPHASE):
  516. PHASES[voice] = 0.0
  517. vrgb = [int(i*255) for i in colorsys.hls_to_rgb(float(voice) / STREAMS * 2.0 / 3.0, 0.5, 1.0)]
  518. frgb = rgb_for_freq_amp(pkt.data[2], pkt.as_float(3))
  519. print '\x1b[1;32mPLAY',
  520. print '\x1b[1;38;2;{};{};{}mVOICE'.format(*vrgb), '{:03}'.format(voice),
  521. print '\x1b[1;38;2;{};{};{}mFREQ'.format(*frgb), '{:04}'.format(pkt.data[2]), 'AMP', '%08.6f'%pkt.as_float(3),
  522. if pkt.data[5] & PLF.SAMEPHASE:
  523. print '\x1b[1;37mSAMEPHASE',
  524. if pkt.data[0] == 0 and pkt.data[1] == 0:
  525. print '\x1b[1;35mSTOP!!!'
  526. else:
  527. print '\x1b[1;36mDUR', '%08.6f'%dur
  528. #signal.setitimer(signal.ITIMER_REAL, dur)
  529. elif pkt.cmd == CMD.CAPS:
  530. data = [0] * 8
  531. data[0] = STREAMS
  532. data[1] = stoi(IDENT)
  533. for i in xrange(len(UID)/4 + 1):
  534. data[i+2] = stoi(UID[4*i:4*(i+1)])
  535. sock.sendto(str(Packet(CMD.CAPS, *data)), cli)
  536. print '\x1b[1;34mCAPS'
  537. elif pkt.cmd == CMD.PCM:
  538. fdata = data[4:]
  539. fdata = struct.pack('16i', *[i<<16 for i in struct.unpack('16h', fdata)])
  540. QUEUED_PCM += fdata
  541. #print 'Now', len(QUEUED_PCM) / 4.0, 'frames queued'
  542. elif pkt.cmd == CMD.PCMSYN:
  543. print '\x1b[1;37mPCMSYN',
  544. bufamt = pkt.data[0]
  545. print '\x1b[0m DESBUF={}'.format(bufamt),
  546. if LAST_SYN is None:
  547. LAST_SYN = time.time()
  548. else:
  549. dt = time.time() - LAST_SYN
  550. dfr = dt * RATE
  551. bufnow = len(QUEUED_PCM) / 4
  552. print '\x1b[35m CURBUF={}'.format(bufnow),
  553. if bufnow != 0:
  554. DRIFT_FACTOR = 1.0 + float(bufnow - bufamt) / (bufamt * dfr * options.pcm_corr_rate)
  555. print '\x1b[37m (DRIFT_FACTOR=%08.6f)'%(DRIFT_FACTOR,),
  556. print
  557. else:
  558. print '\x1b[1;31mUnknown cmd', pkt.cmd