client.py 24 KB

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