client.py 19 KB

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