client.py 26 KB

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