client.py 31 KB

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