[jokosher-devel] [PATCH] VU-meter for monitoring input on each Instrument (bug #70351)



Hello,

I wanted to learn some gstreamer to better understand what goes on beneath the scenes in Jokosher, and ended up with this:
I've created a gstreamer pipeline for each InstrumentViewer that listens for input and displays the peak and decay in a small VUMeter,
like this: http://www.kryogenix.org/random/jokosher-vumeter.gif
The visual style is equal to the VUWidget in the MixerStrip (some redundancy here, that I might clean up later), only with an added
line to indicate the decay level.
The VUMeter only gets updated when the track is armed, and is not visible in the small view.

Hope you like it, I'm leaving for a two-week vacation tomorrow, so it might be a while until I can answer questions or fix bugs...:)

Regards,
Knut Erik Teigen
Index: InstrumentViewer.py
===================================================================
--- InstrumentViewer.py	(revision 1433)
+++ InstrumentViewer.py	(working copy)
@@ -9,8 +9,10 @@
 #-------------------------------------------------------------------------------
 
 import gtk
+import gst
 import pango
 from EventLaneViewer import *
+from VUMeter import *
 import Globals
 import AddInstrumentDialog
 import ControlsBox
@@ -41,6 +43,9 @@
 	DRAG_TARGETS = [ ( "jokosher_instr_move", 	# A custom name for the instruments
 					   gtk.TARGET_SAME_APP,		# Only move inside Jo
 					   INSTR_DRAG_TYPE )]		# Use the custom number
+
+        """ How many times per second the VUMeter is updated """
+        FPS=5
 	
 	#_____________________________________________________________________
 	
@@ -81,8 +86,11 @@
 		self.eventLane = EventLaneViewer(project, instrument, self, mainview, self.small)
 		
 		self.mainBox.pack_start(self.headerAlign, False, False)
+
+
 		self.mainBox.pack_end(self.eventLane, True, True)
 
+
 		# create track header bits
 		self.labelbox = gtk.HBox()
 		self.labelbox.set_spacing(6)
@@ -118,7 +126,12 @@
 			self.image.set_from_pixbuf(pb)
 		
 		self.labelbox.pack_start(self.image, False)
-		self.labelbox.pack_end(self.labeleventbox)
+		self.labelbox.pack_start(self.labeleventbox,False)
+
+#                 self.vumeter = VUMeter()
+                self.vumeter = VUMeter(self,mainview)
+		self.labelbox.pack_end(self.vumeter,True)
+
 		self.headerBox.pack_start(self.labelbox)
 		self.controlsBox = ControlsBox.ControlsBox(project,mainview,instrument,includeEffects=True)
 		self.headerBox.pack_start(self.controlsBox, False)
@@ -140,10 +153,19 @@
 		self.headerEventBox.connect('drag_drop', self.OnDragDrop)
 		self.headerEventBox.connect('drag_end', self.OnDragEnd)
 		
+		self.instrument.connect("arm", self.OnArm)
 		self.instrument.connect("name", self.OnInstrumentName)
 		self.instrument.connect("image", self.OnInstrumentImage)
 		self.instrument.connect("selected", self.OnInstrumentSelected)
 
+                # Create separate pipeline and listen for messages
+                s = 'alsasrc device=%s ! level interval=%d message=true ! fakesink' % (self.instrument.input,gst.SECOND/self.FPS)
+		self.inputListenerPipeline = gst.parse_launch(s)
+ 		self.bus = self.inputListenerPipeline.get_bus()
+		self.bus.add_signal_watch()
+                self.message = self.bus.connect("message::element",self.OnUpdateVUMeter)
+                self.inputListenerPipeline.set_state(gst.STATE_PLAYING)
+		
 		#set the appropriate colour if the instrument it already selected.
 		self.OnInstrumentSelected()
 		self.show_all()
@@ -152,7 +174,42 @@
 			self.controlsBox.hide()
 
 	#_____________________________________________________________________
+        
+        def OnUpdateVUMeter(self,bus,message):
+                """
+                Updates the VUMeter for this Instrument when a message is 
+                received from the bus.
 
+                Parameters:
+                        bus -- the bus that sent the message.
+                        message -- the message containing level information.
+                """
+
+                if message and message.structure.get_name() == 'level' and self.instrument.isArmed:
+                        s = message.structure
+                        peak  = s['peak'][0]
+                        decay  = s['decay'][0]
+                        self.vumeter.SetLevel(peak,decay)
+                return True
+	#_____________________________________________________________________
+
+
+	def OnArm(self, widget):
+		"""
+		Toggles arming the instrument on/off.
+		It will also update the pressed in/out look of the button.
+		
+		Parameters:
+			widget -- reserved for GTK callbacks, don't use it explicitly.
+		"""
+                if self.instrument.isArmed:
+                        self.inputListenerPipeline.set_state(gst.STATE_PLAYING)
+                else:
+                        self.inputListenerPipeline.set_state(gst.STATE_READY)
+                        self.vumeter.SetLevel(-90.0,-90.0)
+		
+	#_____________________________________________________________________
+
 	def OnSelect(self, widget, event):
 		"""
 		Called when a button has been pressed anywhere within InstrumentViewer.
@@ -282,7 +339,6 @@
 		Parameters:
 			instrument -- the instrument instance that send the signal.
 		"""
-
 		self.instrlabel.set_text(self.instrument.name)
 	
 	#_____________________________________________________________________
@@ -322,7 +378,6 @@
 
 	#______________________________________________________________________
 
-
 	def OnDragMotion(self, widget, context, x, y, time):
 		"""
 		Called each time the user moves the mouse while dragging.
@@ -490,10 +545,12 @@
 		if self.small:
 			pb = self.instrument.pixbuf.scale_simple(20, 20, gtk.gdk.INTERP_BILINEAR)
 			self.image.set_from_pixbuf(pb)
+                        self.vumeter.hide()
 			self.controlsBox.hide()
 			self.separator.show()
 		else:
 			self.image.set_from_pixbuf(self.instrument.pixbuf)
+                        self.vumeter.show()
 			self.controlsBox.show()
 			self.separator.hide()
 
Index: VUMeter.py
===================================================================
--- VUMeter.py	(revision 0)
+++ VUMeter.py	(revision 0)
@@ -0,0 +1,198 @@
+#
+#	THIS FILE IS PART OF THE JOKOSHER PROJECT AND LICENSED UNDER THE GPL. SEE
+#	THE 'COPYING' FILE FOR DETAILS
+#
+#	VUMeter.py
+#	
+#	A small VUMeter that shows the input level for each Instrument
+#
+#-------------------------------------------------------------------------------
+import gtk
+from gtk import gdk
+import gobject
+import cairo
+
+class VUMeter(gtk.DrawingArea):
+
+        """
+	Various color configurations:
+	   ORGBA = Offset, Red, Green, Blue, Alpha
+	   RGBA = Red, Green, Blue, Alpha
+	   RGB = Red, Green, Blue
+	"""
+	_VH_ACTIVE_RGBA = (1., 1., 1., 1.)
+	_VH_INACTIVE_RGBA = (1., 1., 1., 0.75)
+	_VH_BORDER_RGBA = (0.5, 0., 0., 0.75)
+	_BACKGROUND_RGB = (0., 0., 0.)
+	_LEVEL_GRADIENT_BOTTOM_ORGBA = (1, 0, 1, 0, 1)
+	_LEVEL_GRADIENT_TOP_ORGBA = (0, 1, 0, 0, 1)
+	_TEXT_RGBA = (0., 0., 0., 1.)
+
+	
+	#_____________________________________________________________________
+	
+	def __init__(self, viewer, mainview):
+		"""
+		Creates a new instance of VUMeter.
+		
+		Parameters:
+			viewer -- InstrViewer that has this widget
+			mainview -- the main Jokosher window (JokosherApp).
+		"""
+		gtk.DrawingArea.__init__(self)
+
+		self.viewer = viewer
+		self.mainview = mainview
+
+                self.bordersize = 2
+                        
+                self.connect("configure_event", self.OnSizeChanged)
+                self.connect("size-request", self.SetSize)
+                self.connect("expose-event", self.OnDraw)
+
+                self.source = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.allocation.width, self.allocation.height)
+
+                self.peak=-90.
+                self.decay=-90.
+		
+	#_____________________________________________________________________
+
+        def ScaleIEC(self, db):
+                """
+                Scales the decibel level according to the standard, IEC-268-18.
+                
+                Parameters:
+                        db -- The decibel level.
+
+                Returns:
+                        The percentage to scale the decibel level to.
+
+                """
+
+                pct = 0.0
+
+                if db < -70.0:
+                        pct = 0.0
+                elif db < -60.0:
+                        pct = (db + 70.0) * 0.25
+                elif db < -50.0:
+                        pct = (db + 60.0) * 0.5 + 2.5
+                elif db < -40.0:
+                        pct = (db + 50.0) * 0.75 + 7.5
+                elif db < -30.0:
+                        pct = (db + 40.0) * 1.5 + 15.0
+                elif db < -20.0:
+                        pct = (db + 30.0) * 2.0 + 30.0
+                elif db < 0.0:
+                        pct = (db + 20.0) * 2.5 + 50.0
+                else:
+                        pct = 100.0
+
+                return pct
+
+	#_____________________________________________________________________
+
+        def SetLevel(self,peak,decay):
+                self.peak=peak
+                self.decay=decay
+                self.queue_draw()
+	
+	#_____________________________________________________________________
+
+        def SetSize(self,asdf,request):
+                """
+                Sets the size of the VUMeter.
+                """
+
+                # TODO - Change to globals.
+                request.width=10
+                request.height=20
+
+	#_____________________________________________________________________
+
+	def OnSizeChanged(self, obj, evt):
+                """
+                Toggles a redraw of the VUWidget if needed.
+                
+                Parameters:
+                        obj -- reserved for Cairo callbacks, don't use it explicitly. *CHECK*
+                        evt --reserved for Cairo callbacks, don't use it explicitly. *CHECK*
+                """
+
+                # TODO create VUUtils.py for the functions that are almost equal in both VUMeter and VUWidget.
+                if self.allocation.width != self.source.get_width() or self.allocation.height != self.source.get_height():
+                        self.source = cairo.ImageSurface(cairo.FORMAT_ARGB32, self.allocation.width, self.allocation.height)
+                        self.GenerateBackground()
+
+	#_____________________________________________________________________
+
+	def GenerateBackground(self):
+		"""
+		Renders the gradient strip for the VU meter background to speed up drawing.
+		"""
+		
+		rect = self.get_allocation()
+
+		ctx = cairo.Context(self.source)
+		ctx.set_line_width(1)
+		ctx.set_antialias(cairo.ANTIALIAS_SUBPIXEL)
+		
+		# Create our green to red gradient
+		pat = cairo.LinearGradient(0.0, 0.0, 0, rect.height-2*self.bordersize)
+		pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_BOTTOM_ORGBA)
+		pat.add_color_stop_rgba(*self._LEVEL_GRADIENT_TOP_ORGBA)
+
+		# Fill the volume bar
+		ctx.rectangle(self.bordersize, self.bordersize, rect.width-2*self.bordersize, rect.height-2*self.bordersize)
+		ctx.set_source(pat)
+		ctx.fill()
+	#_____________________________________________________________________
+
+	def OnDraw(self, widget, event):
+		"""
+		Handles the GTK paint event.
+		
+		Parameters:
+			widget -- reserved for GTK callbacks, don't use it explicitly.
+			event -- reserved for GTK callbacks, don't use it explicitly.
+			
+		Returns:
+			False -- TODO
+		"""
+		ctx = widget.window.cairo_create()
+		
+		rect = self.get_allocation()
+                vu_width = rect.width-2*self.bordersize
+                vu_height = rect.height-2*self.bordersize
+
+                peaklevelpct = self.ScaleIEC(self.peak)
+                peakheight = int(vu_height * (peaklevelpct / 100))
+
+		# Fill a black background		
+		ctx.rectangle(self.bordersize, self.bordersize, vu_width, vu_height)
+		ctx.set_source_rgb(*self._BACKGROUND_RGB)
+		ctx.fill()
+
+		# Clip cached gradient backgound according to peakheight
+                if peakheight>0:
+                        ctx.rectangle(self.bordersize, vu_height-peakheight, vu_width, vu_height+self.bordersize)
+                        ctx.clip()
+                        ctx.set_source_surface(self.source, 0, 0)	
+                        ctx.paint()
+
+		# Reset the clip region
+		ctx.reset_clip()
+
+                # Draw decay line
+                decaylevelpct = self.ScaleIEC(self.decay)
+                decayheight = int(vu_height * (decaylevelpct / 100))
+                if self.decay > -90.0:
+                        if decayheight<=0:
+                                decayheight=1
+                        ctx.set_source_rgb(.9,.9,0.0) # yellow (TODO change color?)
+                        ctx.move_to(self.bordersize,vu_height-decayheight)
+                        ctx.rel_line_to(vu_width,0.0)
+                        ctx.stroke()
+		
+		return False
+		
 #=========================================================================


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]