summaryrefslogtreecommitdiff
path: root/voctogui/lib/audioleveldisplay.py
blob: 1d91d357ba1a8af5b86a56b7db5572c024847ebc (plain)
  1. import logging, math
  2. from gi.repository import Gst, Gtk
  3. class AudioLevelDisplay(object):
  4. """ Displays a Level-Meter of another VideoDisplay into a GtkWidget """
  5. def __init__(self, drawing_area):
  6. self.log = logging.getLogger('AudioLevelDisplay[%s]' % drawing_area.get_name())
  7. self.drawing_area = drawing_area
  8. self.levelrms = []
  9. self.levelpeak = []
  10. self.leveldecay = []
  11. self.drawing_area.connect('draw', self.on_draw)
  12. def on_draw(self, widget, cr):
  13. channels = len(self.levelrms)
  14. if channels == 0:
  15. return
  16. width = self.drawing_area.get_allocated_width()
  17. height = self.drawing_area.get_allocated_height()
  18. margin = 2 # px
  19. channel_width = int((width - (margin * (channels - 1))) / channels)
  20. # self.log.debug(
  21. # 'width: %upx filled with %u channels of each %upx '
  22. # 'and %ux margin of %upx',
  23. # width, channels, channel_width, channels-1, margin)
  24. rms_px = [ self.normalize_db(db) * height for db in self.levelrms ]
  25. peak_px = [ self.normalize_db(db) * height for db in self.levelpeak ]
  26. decay_px = [ self.normalize_db(db) * height for db in self.leveldecay ]
  27. cr.set_line_width(channel_width)
  28. for y in range(0, height):
  29. pct = self.clamp(((y / height) - 0.6) / 0.42, 0, 1)
  30. for channel in range(0, channels):
  31. x = (channel * channel_width) + (channel * margin)
  32. bright = 0.25
  33. if int(y - decay_px[channel]) in range(0, 2):
  34. bright = 1.5
  35. elif y < rms_px[channel]:
  36. bright = 1
  37. elif y < peak_px[channel]:
  38. bright = 0.75
  39. cr.set_source_rgb(pct * bright, (1-pct) * bright * 0.75, 0 * bright)
  40. cr.move_to(x, height-y)
  41. cr.line_to(x + channel_width, height-y)
  42. cr.stroke()
  43. cr.set_source_rgb(0,0,0)
  44. cr.move_to(x + channel_width, height-y)
  45. cr.line_to(x + channel_width + margin, height-y)
  46. cr.stroke()
  47. cr.set_source_rgb(1, 1, 1)
  48. for db in [-40, -20, -10, -5, -4, -3, -2, -1]:
  49. text = str(db)
  50. xbearing, ybearing, textwidth, textheight, xadvance, yadvance = (
  51. cr.text_extents(text))
  52. y = self.normalize_db(db) * height
  53. cr.move_to((width-textwidth) / 2, height - y - textheight)
  54. cr.show_text(text)
  55. return True
  56. def normalize_db(self, db):
  57. # -60db -> 1.00 (very quiet)
  58. # -30db -> 0.75
  59. # -15db -> 0.50
  60. # -5db -> 0.25
  61. # -0db -> 0.00 (very loud)
  62. logscale = 1 - math.log10(-0.15 * db + 1)
  63. normalized = self.clamp(logscale, 0, 1)
  64. return normalized
  65. def clamp(self, value, min_value, max_value):
  66. return max(min(value, max_value), min_value)
  67. def level_callback(self, rms, peak, decay):
  68. self.levelrms = rms
  69. self.levelpeak = peak
  70. self.leveldecay = decay
  71. self.drawing_area.queue_draw()