render.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  1. # A visualizer for the Python client (or any other client) rendering to a mapped file
  2. import optparse
  3. import mmap
  4. import os
  5. import time
  6. import struct
  7. import colorsys
  8. import math
  9. import pygame
  10. import pygame.gfxdraw
  11. parser = optparse.OptionParser()
  12. parser.add_option('--map-file', dest='map_file', default='client_map', help='File mapped by -G mapped')
  13. parser.add_option('--map-samples', dest='map_samples', type='int', default=4096, help='Number of samples in the map file (MUST agree with client)')
  14. parser.add_option('--pg-samp-width', dest='samp_width', type='int', help='Set the width of the sample pane (by default display width / 2)')
  15. parser.add_option('--pg-fullscreen', dest='fullscreen', action='store_true', help='Use a full-screen video mode')
  16. parser.add_option('--pg-no-colback', dest='no_colback', action='store_true', help='Don\'t render a colored background')
  17. parser.add_option('--pg-low-freq', dest='low_freq', type='int', default=40, help='Low frequency for colored background')
  18. parser.add_option('--pg-high-freq', dest='high_freq', type='int', default=1500, help='High frequency for colored background')
  19. parser.add_option('--pg-log-base', dest='log_base', type='int', default=2, help='Logarithmic base for coloring (0 to make linear)')
  20. options, args = parser.parse_args()
  21. while not os.path.exists(options.map_file):
  22. print 'Waiting for file to exist...'
  23. time.sleep(1)
  24. f = open(options.map_file)
  25. mapping = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
  26. f.close()
  27. fixfmt = '>f'
  28. fixfmtsz = struct.calcsize(fixfmt)
  29. sigfmt = '>' + 'f' * options.map_samples
  30. sigfmtsz = struct.calcsize(sigfmt)
  31. strfmtsz = len(mapping) - fixfmtsz - sigfmtsz
  32. print 'Map size:', len(mapping), 'Appendix size:', strfmtsz
  33. print 'Size triple:', fixfmtsz, sigfmtsz, strfmtsz
  34. STREAMS = strfmtsz / struct.calcsize('>Lf')
  35. strfmt = '>' + 'Lf' * STREAMS
  36. print 'Detected', STREAMS, 'streams'
  37. pygame.init()
  38. WIDTH, HEIGHT = 640, 480
  39. dispinfo = pygame.display.Info()
  40. if dispinfo.current_h > 0 and dispinfo.current_w > 0:
  41. WIDTH, HEIGHT = dispinfo.current_w, dispinfo.current_h
  42. flags = 0
  43. if options.fullscreen:
  44. flags |= pygame.FULLSCREEN
  45. disp = pygame.display.set_mode((WIDTH, HEIGHT), flags)
  46. WIDTH, HEIGHT = disp.get_size()
  47. SAMP_WIDTH = WIDTH / 2
  48. if options.samp_width:
  49. SAMP_WIDTH = options.samp_width
  50. BGR_WIDTH = WIDTH - SAMP_WIDTH
  51. HALFH = HEIGHT / 2
  52. PFAC = HEIGHT / 128.0
  53. sampwin = pygame.Surface((SAMP_WIDTH, HEIGHT))
  54. sampwin.set_colorkey((0, 0, 0))
  55. lastsy = HALFH
  56. bgrwin = pygame.Surface((BGR_WIDTH, HEIGHT))
  57. bgrwin.set_colorkey((0, 0, 0))
  58. clock = pygame.time.Clock()
  59. font = pygame.font.SysFont(pygame.font.get_default_font(), 24)
  60. def rgb_for_freq_amp(f, a):
  61. a = max((min((a, 1.0)), 0.0))
  62. pitchval = float(f - options.low_freq) / (options.high_freq - options.low_freq)
  63. if options.log_base == 0:
  64. try:
  65. pitchval = math.log(pitchval) / math.log(options.log_base)
  66. except ValueError:
  67. pass
  68. bgcol = colorsys.hls_to_rgb(min((1.0, max((0.0, pitchval)))), 0.5 * (a ** 2), 1.0)
  69. return [int(i*255) for i in bgcol]
  70. while True:
  71. DISP_FACTOR = struct.unpack(fixfmt, mapping[:fixfmtsz])[0]
  72. LAST_SAMPLES = struct.unpack(sigfmt, mapping[fixfmtsz:fixfmtsz+sigfmtsz])
  73. VALUES = struct.unpack(strfmt, mapping[fixfmtsz+sigfmtsz:])
  74. FREQS, AMPS = VALUES[::2], VALUES[1::2]
  75. if options.no_colback:
  76. disp.fill((0, 0, 0), (0, 0, WIDTH, HEIGHT))
  77. else:
  78. gap = WIDTH / STREAMS
  79. for i in xrange(STREAMS):
  80. FREQ = FREQS[i]
  81. AMP = AMPS[i]
  82. if FREQ > 0:
  83. bgcol = rgb_for_freq_amp(FREQ, AMP)
  84. else:
  85. bgcol = (0, 0, 0)
  86. disp.fill(bgcol, (i*gap, 0, gap, HEIGHT))
  87. bgrwin.scroll(-1, 0)
  88. bgrwin.fill((0, 0, 0), (BGR_WIDTH - 1, 0, 1, HEIGHT))
  89. for i in xrange(STREAMS):
  90. FREQ = FREQS[i]
  91. AMP = AMPS[i]
  92. if FREQ > 0:
  93. try:
  94. pitch = 12 * math.log(FREQ / 440.0, 2) + 69
  95. except ValueError:
  96. pitch = 0
  97. else:
  98. pitch = 0
  99. col = [min(max(int(AMP * 255), 0), 255)] * 3
  100. bgrwin.fill(col, (BGR_WIDTH - 1, HEIGHT - pitch * PFAC - PFAC, 1, PFAC))
  101. sampwin.fill((0, 0, 0), (0, 0, SAMP_WIDTH, HEIGHT))
  102. x = 0
  103. for i in LAST_SAMPLES:
  104. sy = int(AMP * HALFH + HALFH)
  105. pygame.gfxdraw.line(sampwin, x - 1, lastsy, x, sy, (0, 255, 0))
  106. x += 1
  107. lastsy = sy
  108. disp.blit(bgrwin, (0, 0))
  109. disp.blit(sampwin, (BGR_WIDTH, 0))
  110. if DISP_FACTOR != 0:
  111. tsurf = font.render('%+011.6g'%(DISP_FACTOR,), True, (255, 255, 255), (0, 0, 0))
  112. disp.fill((0, 0, 0), tsurf.get_rect())
  113. disp.blit(tsurf, (0, 0))
  114. pygame.display.flip()
  115. for ev in pygame.event.get():
  116. if ev.type == pygame.KEYDOWN:
  117. if ev.key == pygame.K_ESCAPE:
  118. pygame.quit()
  119. exit()
  120. elif ev.type == pygame.QUIT:
  121. pygame.quit()
  122. exit()
  123. if not os.path.exists(options.map_file):
  124. pygame.quit()
  125. exit()
  126. clock.tick(60)