MeterGadget.pbi, create analog style meters for your program

Share your advanced PureBasic knowledge/code with the community.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

MeterGadget.pbi, create analog style meters for your program

Post by BasicallyPure »

This include file will create meter gadgets for you.
Use procedure MeterGadget(gadNum.i, x.i, y.i, size.i, caption.s = "", style.i = 0) to create your meter gadget.
You control the needle positoin using the procedure SetMeterState(GadNum.i, position.f)
The meter gadget has a built in smoothing function that you can activate with SetMeterSmoothing(GadNum.i, value.i)

Edit: 3/29/2013
Added optional flags to draw the needle using anti-aliasing lines.
Using one of these options will result in better looking needles but CPU load will increase.
On my system CPU load increased 3% to 5% with anti-aliasing style needles on two meters.

Edit: 3/28/2013
Made the peak LED an option set with a flag and is now available for both linear and VU meter types.
Added alpha blending to the bottom of the meter for more realistic appearance.
Minor improvement in needle appearance when deflection is far left or far right of center.

BP

Image

Code: Select all

; Name:    MeterGadget.pbi
; Version: 1.2
; Author:  BasicallyPure
; Date:    3.29.2013
; OS:      Windows, Linux, Mac
; PB ver.: 5.11
; License: Free
;
; Syntax: result = MeterGadget(#gadget, x, y, size, [caption], [flags])
;   #Gadget  | number to identify the gadget. #PB_Any can be used to auto-generate.
;   x, y     | location of the meter gadget.
;   size     | width of meter, height is automatically calculated as size / 1.5.
;   caption  | the text to display on the meter face.
;
;   flags    | flags may be combined by using | (bitwise OR operator).
;            | #MTR_STYLE_VU, creates a VU style meter, default is linear style.
;            | #MTR_STYLE_LED, adds a peak indicator LED to any meter.
;            | the default needle is drawn without anti-aliased lines.
;            | using the following needle style flags will increase CPU usage.
;            | #MTR_NEEDLE_VAR1, draw needle with a single anti-aliased thick line.
;            | #MTR_NEEDLE_VAR2, draw outlined needle with anti-aliased lines.
;            | #MTR_NEEDLE_VAR3, draw filled needle with multiple anti-aliased calls.
;
; use SetMeterState(#gadget, position) to set needle position.
; position range is from 0 to 100.  Values outside this range are clipped.
;
; GetMeterState(#gadget) returns the needle position.
;
; SetMeterSmoothing(#gadget, value) applies a smooting function to the needle movement.
;        valid smoothing values are 0, 2, 4, 8, 16, 32, 64, 128.
;
; if you want to change the default meter options use these procedures.
; the procedures should be called before the meter is created.
;
; setMeterGradientColor(C1, C2, C3, C4) | omit all parameters to reset default colors.
; SetMeterCaptionFont(fontNumber)       | omit parameter to return to default font.
; SetMeterScaleFont(fontNumber)         | omit parameter to return to default font.
; SetMeterCaptionColor(color)           | omit parameter to return to default color.
; SetMeterScaleColor(color)             | omit parameter to return to default color.
; SetMeterNeedleColor(color)            | omit parameter to return to default color.
; SetMeterBottomColor(color)            | omit parameter to return to default color.

EnableExplicit

Structure MeterType
   width.i        ; meter width
   height.i       ; meter height
   flags.i        ; option flags, #MTR_STYLE_vu | #MTR_STYLE_LED
   position.f     ; needle position, returned with GetMeterState(GadNum)
   captionFont.i  ; use this font for meter caption
   captionColor.i ; caption text color
   scaleFont.i    ; use this font for scale labels
   scaleColor.i   ; color used for drawing scale and scale labels
   caption.s      ; meter caption
   gradColor_1.i  ; first color for gradient
   gradColor_2.i  ; second color for gradient
   gradColor_3.i  ; third color for gradient
   gradColor_4.i  ; forth color for gradient
   needleColor.i  ; color used to draw needle
   bottomColor.i  ; color of bottom region of meter face
   clip.i         ; indicates input exceeded maximum value (100)
   LED_radius.i   ; radius of LED
   LED_X.i        ; x location of peak LED indicator
   LED_Y.i        ; y location of peak LED indicator
   smoothing.i    ; smoothing value ( power of 2 up to 128 )
   buffer.f[128]  ; smoothing buffer
   bufferIdx.a    ; smoothing buffer index
   bufferSum.f    ; running sum of smoothing buffer
   mid_X.i        ; middle of meter (x axis)
   arcRadius.i    ; sets position of scale arc
   backImage.i    ; meter background image
   bottomImage.i  ; decorative bar at bottom of meter
EndStructure

Structure DefaultType
   gradientC_1.i
   gradientC_2.i
   gradientC_3.i
   gradientC_4.i
   captionFont.i
   captionColor.i
   scaleFont.i
   scaleColor.i
   needleColor.i
   bottomColor.i
EndStructure

;#MTR_STYLE_STD   = %0000
#MTR_STYLE_VU    = %0001
#MTR_STYLE_LED   = %0010
#MTR_NEEDLE_STD  = %0000
#MTR_NEEDLE_VAR1 = %0100
#MTR_NEEDLE_VAR2 = %1000
#MTR_NEEDLE_VAR3 = %1100

Define MtrDefaults.DefaultType
   MtrDefaults\gradientC_1  = $60FFFF ;$00FFFF
   MtrDefaults\gradientC_2  = $80DFDF ;$00FF00
   MtrDefaults\gradientC_3  = $A0BFBF ;$00FF80
   MtrDefaults\gradientC_4  = $FFAFAF ;$0000FF
   MtrDefaults\captionColor = 0
   MtrDefaults\captionFont  = LoadFont(#PB_Any,"Arial", 10, #PB_Font_Bold)
   MtrDefaults\scaleFont    = LoadFont(#PB_Any,"Arial", 8)
   MtrDefaults\scaleColor   = $FF0000
   MtrDefaults\needleColor  = $0000FF
   MtrDefaults\bottomColor  = $603030
   
Define mtr_default_caption_font = MtrDefaults\captionFont
Define mtr_default_Scale_font   = MtrDefaults\scaleFont

Dim Meter.MeterType(1)

NewMap MeterIndex.i() ; a Map to index Meter() with gadget number

Declare Draw_Meter(meterNumber)
Declare SetMeterState(meterNumber.i, position.f)
Declare g2D_AA_BlendColor(Color1.i, Color2.i, Blend.d)
Declare g2D_LineXY(x.i, y.i, x3.i, y3.i, Color.i=0, Thickness.i=1)

Procedure MeterGadget(gadNum.i, x.i, y.i, size.i, caption.s = "", flags.i = 0)
   
   Shared Meter() ; structured array
   Shared MeterIndex() ; Map
   Shared MtrDefaults ; structure
   Static meterCount = 1
   Protected result.i, w.i, h.i
   
   If flags < 0 Or flags > %1111 : ProcedureReturn 0 : EndIf
   If size < 150 : size = 150 : EndIf
   w = size : h = w / 1.5
   
   result = CanvasGadget(gadNum, x, y, w, h, #PB_Canvas_Border)
   If result <> 0
      If gadNum = #PB_Any : gadNum = result : EndIf
      If meterCount > 1 : ReDim Meter(meterCount) : EndIf
      
      With Meter(meterCount)
         \width        = w - 4 ; adjusted for 4 pixel border of canvas gadget
         \height       = h - 4 ; adjusted for 4 pixel border of canvas gadget
         \flags        = flags
         \gradColor_1  = MtrDefaults\gradientC_1
         \gradColor_2  = MtrDefaults\gradientC_2
         \gradColor_3  = MtrDefaults\gradientC_3
         \gradColor_4  = MtrDefaults\gradientC_4
         \captionColor = MtrDefaults\captionColor
         \captionFont  = MtrDefaults\captionFont
         \scaleFont    = MtrDefaults\scaleFont
         \scaleColor   = MtrDefaults\scaleColor
         \needleColor  = MtrDefaults\needleColor
         \bottomColor  = MtrDefaults\bottomColor
         \smoothing    = 0
         \bufferIdx    = 0
         \bufferSum    = 0
         \LED_radius   = \width / 40
         \LED_X        = \width - 13 - 3*\LED_radius
         \LED_Y        = \height - \height * 0.35 - 2*\LED_radius
         \caption      = caption
         \mid_X        = \width / 2
         \arcRadius    = h * (w / (w + 50.0)) ; this locates the scale arc
         \backImage    = CreateImage(#PB_Any, \width, \height, 32)
         \bottomImage  = CreateImage(#PB_Any, \width, \height * 0.2, 32)
         
         MeterIndex(Str(gadNum)) = meterCount
         
         Draw_Meter(meterCount)
         
         StartDrawing(CanvasOutput(gadNum))
            DrawImage(ImageID(\backImage),0 ,0)
         StopDrawing()
         
      EndWith
      
      SetMeterState(gadNum, 0)
         
      MeterCount + 1
   EndIf
   
   ProcedureReturn result
EndProcedure

Procedure Draw_Meter(meterNumber)
   Shared Meter()
   Protected x.i, y.i, px.i, py.i, angle.f, inc.f
   Protected vu.i, sf.f, color.i
   
   With Meter(meterNumber)
      
      StartDrawing(ImageOutput(\bottomImage))
         DrawingMode(#PB_2DDrawing_AlphaChannel)
         Box(0, 0, OutputWidth(), OutputHeight(), $00000000)
         DrawingMode(#PB_2DDrawing_AlphaBlend)
         RoundBox(0, 0, \width, \height * 0.3, \width / 20, \width / 20, $B0000000 + \bottomColor)
      StopDrawing()
      
      StartDrawing(ImageOutput(\backImage))
         DrawingMode(#PB_2DDrawing_Gradient)
         BackColor(\gradColor_4)
         GradientColor(0.33, \gradColor_3)
         GradientColor(0.66, \gradColor_2)
         FrontColor(\gradColor_1)
         LinearGradient(0, \height, 0, 0)
         Box(0, 0 ,\width, \height)
         DrawingMode(#PB_2DDrawing_Transparent)
         DrawingFont(FontID(\scaleFont))
         
         Circle(\mid_X, \height, \width * 0.1, 0)
         Circle(\mid_X, \height - 10, 4, \needleColor ! 1)
         
         inc = 1000 / \width
         angle = -45.0 - inc
         Repeat ; draw scale arc
            angle + inc
            If angle > 45.0 : angle = 45.0 : EndIf
            x = \mid_X  + Sin(Radian(angle)) * \arcRadius
            y = \height - Cos(Radian(angle)) * \arcRadius
            If angle <> -45.0
               LineXY(px,py,x,y,\scaleColor)
            EndIf
            px = x : py = y
         Until angle = 45
         
         If \flags & #MTR_STYLE_VU
            sf = 100 / Pow(10, 3/20)
            vu = 3
            Repeat
               angle = 0.9 * sf * Pow(10, vu/20) - 45
               px = \mid_X  + Sin(Radian(angle)) * (\arcRadius - 5)
               py = \height - Cos(Radian(angle)) * (\arcRadius - 5)
               x  = \mid_X  + Sin(Radian(angle)) * (\arcRadius + 5)
               y  = \height - Cos(Radian(angle)) * (\arcRadius + 5)
               LineXY(px, py, x, y, \scaleColor)
               
               Select vu
                  Case -20, -10, -5, -3, -2, -1, 0, 1, 2, 3
                     If vu < 0 : color = 0 : Else : color = $0000FF : EndIf
                     x  = \mid_X  + Sin(Radian(angle-1)) * (\arcRadius + 22)
                     y  = \height - Cos(Radian(angle-1)) * (\arcRadius + 22)
                     DrawRotatedText(x, y, Str(Abs(vu)), -angle, color)
               EndSelect
         
               Select vu
                  Case -2 To 3 : vu - 1
                  Case -3 : vu - 2
                  Case -5 : vu - 5
                  Case -30 To -10 : vu - 10
               EndSelect
               
            Until vu < -30
            
            DrawingFont(FontID(\captionFont))
            DrawText(10, \height * 0.4, "_", 0)
            DrawText(\width - 15, \height * 0.4 + 5, "+", $0000FF)
            
         Else ; linear style
            angle = -45.0 : inc = 9
            Repeat ; draw scale tic marks
               px = \mid_X  + Sin(Radian(angle)) * (\arcRadius - 5)
               py = \height - Cos(Radian(angle)) * (\arcRadius - 5)
               x  = \mid_X  + Sin(Radian(angle)) * (\arcRadius + 5)
               y  = \height - Cos(Radian(angle)) * (\arcRadius + 5)
               LineXY(px, py, x, y, \scaleColor)
               
               DrawingFont(FontID(\scaleFont))
               x  = \mid_X  + Sin(Radian(angle-1)) * (\arcRadius + 20)
               y  = \height - Cos(Radian(angle-1)) * (\arcRadius + 20)
               DrawRotatedText(x, y, Str(angle/9 + 5), -angle, \captionColor)
               
               angle + inc
            Until angle > 45
         EndIf
         
         If \flags & #MTR_STYLE_LED
            Circle(\LED_X, \LED_Y, \LED_radius + 2, 0)
            Circle(\LED_X, \LED_Y, \LED_radius , $404080)
            DrawingFont(FontID(\scaleFont))
            DrawText(\width - 27 - 3*\LED_radius, \height - \height * 0.35, "PEAK", \captionColor)
         EndIf
         
         If \caption
            DrawingFont(FontID(\captionFont))
            FrontColor(\captionColor)
            x = (\Width  - TextWidth(\caption)) / 2
            y = (\Height - TextHeight(\caption))/ 2
            DrawText(x, y, \caption)
         EndIf
      StopDrawing()
   EndWith
   
EndProcedure

Procedure SetMeterState(GadNum.i, position.f)
   ; call this procedure to adjust the needle position.
   ; range of position is from 0 to 100.
   ; values outside this range are clipped.
   
   Shared Meter(), MeterIndex()
   Protected meterNumber.i = MeterIndex(Str(GadNum))
   Protected mask.a, nw.i, x.i, y.i
   
   If meterNumber = 0
      ProcedureReturn 0 ; Meter number is not valid
   EndIf
   
   If position < 0 : position = 0 : EndIf
   
   With Meter(meterNumber)
      If position >= 100 : \clip = 8 : position = 100 : EndIf
      \position = position
      
      If \smoothing
         mask = \smoothing - 1
         \bufferSum - \buffer[\bufferIdx & mask]
         \buffer[\bufferIdx & mask] = position
         \bufferSum + position
         \bufferIdx + 1
         position = \bufferSum / \smoothing ; this is the running average
      EndIf
      
      position = Radian(0.9 * position - 45)
      x = \mid_X  + Sin(position) * (\arcRadius)
      y = \height - Cos(position) * (\arcRadius)
      
      StartDrawing(CanvasOutput(GadNum))
         
         ; first draw the meter background
         DrawImage(ImageID(\backImage),0 ,0)
         
         ; draw the needle
         Select \flags & %1100
            Case #MTR_NEEDLE_STD
               nw.i = 2 + Round(1.35 - Cos(position), #PB_Round_Nearest)
               Circle(x, y, 2, \needleColor)
               LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
               LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
               LineXY(\mid_X - 4, \height - 10, \mid_X + 4, \height - 10, \needleColor)
               FillArea(\mid_X, \height - 11, \needleColor, \needleColor ! 1)
            Case #MTR_NEEDLE_VAR1 ; draw needle with a single anti-aliased thick line.
               Circle(x, y, 2, \needleColor)
               g2d_LineXY(\mid_X, \height - 10, x, y, \needleColor, 5)
            Case #MTR_NEEDLE_VAR2 ; draw outlined needle with anti-aliased lines.
               nw.i = 2 + Round(1.35 - Cos(position), #PB_Round_Nearest)
               Circle(x, y, 2, \needleColor)
               g2d_LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
               g2d_LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
            Case #MTR_NEEDLE_VAR3 ; draw filled needle with multiple anti-aliased calls.
               nw.i = 2 + Round(1.35 - Cos(position), #PB_Round_Nearest)
               Circle(x, y, 2, \needleColor)
               LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
               LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
               LineXY(\mid_X - 4, \height - 10, \mid_X + 4, \height - 10, \needleColor)
               ; Call FillArea() before color edges are blurred with g2d_LineXY()
               FillArea(\mid_X, \height - 11, \needleColor, \needleColor ! 1)
               g2d_LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
               g2d_LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
         EndSelect
         
         If \flags & #MTR_STYLE_LED
            If \clip : \clip - 1
               Circle(\LED_X, \LED_Y, \LED_radius, $0000FF)
            EndIf
         EndIf
         
         ; draw the bottom region using an alpha blended image
         DrawAlphaImage(ImageID(\bottomImage),0, \height - ImageHeight(\bottomImage))
         
      StopDrawing()
   EndWith
   
EndProcedure

Procedure GetMeterState(GadNum.i)
   Shared Meter(), MeterIndex()
   Protected meterNumber = MeterIndex(Str(GadNum))
   
   If meterNumber = 0
      ProcedureReturn 0 ; Meter number is not valid
   EndIf
   
   ProcedureReturn Meter(meterNumber)\position
EndProcedure

Procedure SetMeterSmoothing(GadNum.i, value.i)
   ; valid smoothing values are 0, 2, 4, 8, 16, 32, 64, 128
   ; other values will be range limited and converted to a power of 2
   
   Shared Meter(), MeterIndex()
   Protected n.i, meterNumber = MeterIndex(Str(GadNum))
   
   If meterNumber = 0
      ProcedureReturn 0 ; Meter number is not valid
   EndIf
   
   If value > 128 : value = 128
   ElseIf value < 2 : value = 0
   Else : value = Pow(2, Round(Log(value) / Log(2),#PB_Round_Nearest))
   EndIf
   
   With Meter(meterNumber)
      
      For n = 0 To value - 1
         \buffer[n] = \position
      Next n
      
      \bufferIdx = 0
      \bufferSum = \position * value
      \smoothing = value
   EndWith
   
EndProcedure

Procedure SetMeterCaptionFont(font.i = -1)
   Shared MtrDefaults, mtr_default_caption_font
   If font = -1
      font = mtr_default_caption_font
   EndIf
   
   If IsFont(font)
      MtrDefaults\captionFont = font
   EndIf
EndProcedure

Procedure SetMeterCaptionColor(color.i = 0)
   Shared MtrDefaults
   MtrDefaults\captionColor = color
EndProcedure

Procedure SetMeterScaleFont(font.i = -1)
   Shared MtrDefaults, mtr_default_Scale_font
   If font = -1
      font = mtr_default_Scale_font
   EndIf
   
   If IsFont(font)
      MtrDefaults\scaleFont = font
   EndIf
EndProcedure

Procedure SetMeterScaleColor(color.i = $FF0000)
   Shared MtrDefaults
   MtrDefaults\scaleColor = color
EndProcedure

Procedure SetMeterNeedleColor(color.i = $0000FF)
   Shared MtrDefaults
   MtrDefaults\needleColor = color
EndProcedure

Procedure SetMeterBottomColor(color.i = $603030)
   Shared MtrDefaults
   MtrDefaults\bottomColor = color
EndProcedure

Procedure SetMeterGradientColor(top.i = $60FFFF, mid1.i = $80DFDF, mid2.i = $A0BFBF, bottom.i = $FFAFAF)
   Shared MtrDefaults
   MtrDefaults\gradientC_1 = top
   MtrDefaults\gradientC_2 = mid1
   MtrDefaults\gradientC_3 = mid2
   MtrDefaults\gradientC_4 = bottom
EndProcedure

Procedure.i g2D_AA_BlendColor(Color1.i, Color2.i, Blend.d)
  ; RASHAD, smooths color edges to produce near anti-aliasing
  Protected Red, Green, Blue, Red2, Green2, Blue2
  Red = Color1 & $FF
  Green = Color1 >> 8 & $FF
  Blue = Color1 >> 16
  Red2 = Color2 & $FF
  Green2 = Color2 >> 8 & $FF
  Blue2 = Color2 >> 16
  Red = Red * Blend + Red2 * (1-Blend)
  Green = Green * Blend + Green2 * (1-Blend)
  Blue = Blue * Blend + Blue2 * (1-Blend)
  ProcedureReturn (Red | Green << 8 | Blue << 16)
EndProcedure

Procedure g2D_LineXY(x.i, y.i, x3.i, y3.i, Color.i=0, Thickness.i=1)
  ; RAHSAD, Le Soldat Inconnu and others
  Protected.i Wd = x3 - X
  Protected.i Ht = y3 - Y
  Protected.i SignX, SignY, n, nn, Color_Found
  Protected.d Thick, x2, y2, Application, Hypo, CosPhi, SinPhi
  If Wd >= 0
    SignX = 1
  Else
    SignX = -1
    Wd = - Wd
  EndIf
  If Ht >= 0
    SignY = 1
  Else
    SignY = -1
    Ht = -Ht
  EndIf
  Thick = Thickness / 2
  Hypo = Sqr(Wd * Wd + Ht * Ht)
  CosPhi = Wd / Hypo
  SinPhi = -Sin(ACos(CosPhi))
  For n = -Thickness To Wd + Thickness
    For nn = -Thickness To Ht + Thickness
      x2 = n * CosPhi - nn * SinPhi
      y2 = Abs(n * SinPhi + nn * CosPhi)
      If y2 <= (Thick + 0.5)
        Application =  0.5 + Thick - y2
        If Application > 1
          Application = 1
        EndIf
        If (x2 > -1) And (x2 < (Hypo + 1))
          If x2 < 0
            Application * (1 + x2)
          ElseIf x2 > Hypo
            Application * (1 - x2 + Hypo)
          EndIf
        Else
          Application = 0
        EndIf
        If Application > 0
          If Application < 1
            Color_Found = Point(X + n * SignX, Y + nn * SignY)
            Plot(X + n * SignX, Y + nn * SignY, g2D_AA_BlendColor(Color, Color_Found, Application))
          Else
            Plot(X + n * SignX, Y + nn * SignY, Color)
          EndIf
        EndIf
      EndIf
    Next nn
  Next n
EndProcedure

DisableExplicit
Here is a demonstration.

Code: Select all

; MeterGadgetTest.pb
; by BasicallyPure
; 3.28.2013

IncludeFile"MeterGadget.pbi"

EnableExplicit

Declare FAKE_SIGNAL()

Define flags = #PB_Window_ScreenCentered | #PB_Window_MinimizeGadget | #PB_Window_SystemMenu
OpenWindow(0, 0, 0, 800, 600, "Meter Gadger Test", flags)
SetWindowColor(0,$300000)

AddWindowTimer(0,0,100)

flags = #MTR_STYLE_VU | #MTR_STYLE_LED
Define mtr_VU_L    = MeterGadget(#PB_Any, 020, 050, 250, "VU - Left" , flags)
Define mtr_VU_R    = MeterGadget(#PB_Any, 285, 050, 250, "VU - Right", flags)

SetMeterNeedleColor($802020)
SetMeterBottomColor($A0A0A0)
Define mtr_RPM.i   = MeterGadget(#PB_Any, 560, 050, 200, "RPM x 1000")

SetMeterGradientColor($B09090, $7090B0, $B09090, $204020)
SetMeterCaptionColor($404000)
SetMeterNeedleColor($E06060)
SetMeterBottomColor()
Define mtr_Volts.i = MeterGadget(#PB_Any, 020, 330, 150, "Volts")
Define mtr_Amps.i  = MeterGadget(#PB_Any, 180, 330, 150, "Amps")

setMeterNeedleColor($A0D0A0)
setMeterScaleColor($80FF00)
setMeterBottomColor($205020)
setMeterCaptionColor($FFFFFF)
setMeterGradientColor($40A040, $509050, $40A040, $205050)
Define mtr_Watts.i = MeterGadget(#PB_Any, 340, 330, 150, "Watts x 10")

Global track_1 = TrackBarGadget(#PB_Any, 020, 450, 470, 30, 0, 100)
Global track_2 = TrackBarGadget(#PB_Any, 560, 195, 200, 30, 0, 100)
SetGadgetState(track_2, 15) : SetMeterState(mtr_RPM, 15)
Global track_3 = TrackBarGadget(#PB_Any, 20, 230, 200, 30, 1, 7, #PB_TrackBar_Ticks)
SetGadgetState(track_3, 4)
Global chkBox_Smoothing = CheckBoxGadget(#PB_Any,20, 270, 200, 20, " Smoothing = 16")

Define amp_L.f = 50
Define amp_R.f = 50
Define smoothing.a = 0

Repeat
   Select WaitWindowEvent()
      Case #PB_Event_CloseWindow
         Break
      Case #PB_Event_Gadget
         Select EventGadget()
            Case track_1
               SetMeterState(mtr_Volts, GetGadgetState(track_1))
               SetMeterState(mtr_Amps , GetGadgetState(track_1))
               SetMeterState(mtr_Watts, (getMeterState(mtr_Volts)/10) * getMeterState(mtr_Amps)/10)
            Case track_2
               SetMeterState(mtr_RPM, GetGadgetState(track_2))
            Case track_3
               smoothing = Pow(2, GetGadgetState(track_3))
               SetGadgetText(chkBox_Smoothing, " Smoothing = " + Str(smoothing))
               If GetGadgetState(chkBox_Smoothing)
                  setMeterSmoothing(mtr_VU_L, smoothing)
               EndIf
            Case chkBox_Smoothing
               If GetGadgetState(chkBox_Smoothing)
                  smoothing = Pow(2, GetGadgetState(track_3))
                  setMeterSmoothing(mtr_VU_L, smoothing)
               Else
                  setMeterSmoothing(mtr_VU_L, 0)
               EndIf
         EndSelect
      Case #PB_Event_Timer
         If EventTimer() = 0
            FAKE_SIGNAL()
            SetMeterState(mtr_VU_L, amp_L)
            SetMeterState(mtr_VU_R, amp_R)
         EndIf
   EndSelect
ForEver

End

Procedure FAKE_SIGNAL()
   Shared amp_L, amp_R
   Static bias.i = 7
   
   amp_L + Random(16) - bias
   amp_R = amp_L + Random(16) - 8
   
   If amp_L < 0
      amp_L = 0 : bias = 7
   ElseIf amp_L > 100
      amp_L = 100 : bias = 9
   EndIf
   
   If amp_R < 0
      amp_R = 0
   ElseIf amp_R > 100
      amp_R = 100
   EndIf
   
EndProcedure
Last edited by BasicallyPure on Fri Mar 29, 2013 7:37 pm, edited 6 times in total.
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
Kiffi
Addict
Addict
Posts: 1485
Joined: Tue Mar 02, 2004 1:20 pm
Location: Amphibios 9

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Kiffi »

very cool! Thanks a lot!

Greetings ... Kiffi
Hygge
User avatar
Demivec
Addict
Addict
Posts: 4260
Joined: Mon Jul 25, 2005 3:51 pm
Location: Utah, USA

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Demivec »

Very nice! Thanks! :)
User avatar
idle
Always Here
Always Here
Posts: 5836
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Re: MeterGadget.pbi, create analog style meters for your pro

Post by idle »

Great gadget BP, thanks!
Windows 11, Manjaro, Raspberry Pi OS
Image
said
Enthusiast
Enthusiast
Posts: 342
Joined: Thu Apr 14, 2011 6:07 pm

Re: MeterGadget.pbi, create analog style meters for your pro

Post by said »

Very nice :D thanks for sharing
davido
Addict
Addict
Posts: 1890
Joined: Fri Nov 09, 2012 11:04 pm
Location: Uttoxeter, UK

Re: MeterGadget.pbi, create analog style meters for your pro

Post by davido »

Thanks for sharing. 8)

Very nice, indeed. And cross-platform, too. :D
DE AA EB
Joris
Addict
Addict
Posts: 890
Joined: Fri Oct 16, 2009 10:12 am
Location: BE

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Joris »

Beautiful.

Thanks.
Yeah I know, but keep in mind ... Leonardo da Vinci was also an autodidact.
User avatar
luis
Addict
Addict
Posts: 3893
Joined: Wed Aug 31, 2005 11:09 pm
Location: Italy

Re: MeterGadget.pbi, create analog style meters for your pro

Post by luis »

I like the fact all is drawn by you without using bitmaps and the final result is very good.

Thank you for sharing :)
"Have you tried turning it off and on again ?"
A little PureBasic review
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Kwai chang caine »

Splendid :shock:
Exactely like the real vu-meter of my old time when i was young :mrgreen:

Thanks a lot for this great effect 8)
ImageThe happiness is a road...
Not a destination
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Simo_na »

Thank you for sharing :)
Last edited by Simo_na on Fri Mar 29, 2013 3:18 pm, edited 1 time in total.
User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: MeterGadget.pbi, create analog style meters for your pro

Post by BasicallyPure »

The code has been updated.
I made the following changes.

1. Made the peak LED an option set with a flag and is now available for both linear and VU meter types.
2. Added alpha blending to the bottom of the meter for more realistic appearance.
3. Minor improvement in needle appearance when deflection is far left or far right of center.

BP
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
User avatar
skywalk
Addict
Addict
Posts: 4211
Joined: Wed Dec 23, 2009 10:14 pm
Location: Boston, MA

Re: MeterGadget.pbi, create analog style meters for your pro

Post by skywalk »

Very nice!
Some suggestions:
1. Realign the numeral locations along the arc of the meter.
Notice the '3' is higher and '10' is not centered on its tick.
No code suggestion yet...
2. Make the needles anti-aliased lines.
I use this code for slow anti-aliased thick/thin lines...

Code: Select all

Procedure.i g2D_AA_BlendColor(Color1.i, Color2.i, Blend.d)
  ; RASHAD, smooths color edges to produce near anti-aliasing
  Protected Red, Green, Blue, Red2, Green2, Blue2
  Red = Color1 & $FF
  Green = Color1 >> 8 & $FF
  Blue = Color1 >> 16
  Red2 = Color2 & $FF
  Green2 = Color2 >> 8 & $FF
  Blue2 = Color2 >> 16
  Red = Red * Blend + Red2 * (1-Blend)
  Green = Green * Blend + Green2 * (1-Blend)
  Blue = Blue * Blend + Blue2 * (1-Blend)
  ProcedureReturn (Red | Green << 8 | Blue << 16)
EndProcedure

Procedure g2D_LineXY(x.i, y.i, x3.i, y3.i, Color.i=#Black, Thickness.i=1)
  ; RAHSAD, Le Soldat Inconnu and others
  Protected.i Wd = x3 - X
  Protected.i Ht = y3 - Y
  Protected.i SignX, SignY, n, nn, Color_Found
  Protected.d Thick, x2, y2, Application, Hypo, CosPhi, SinPhi
  If Wd >= 0
    SignX = 1
  Else
    SignX = -1
    Wd = - Wd
  EndIf
  If Ht >= 0
    SignY = 1
  Else
    SignY = -1
    Ht = -Ht
  EndIf
  Thick = Thickness / 2
  Hypo = Sqr(Wd * Wd + Ht * Ht)
  CosPhi = Wd / Hypo
  SinPhi = -Sin(ACos(CosPhi))
  For n = -Thickness To Wd + Thickness
    For nn = -Thickness To Ht + Thickness
      x2 = n * CosPhi - nn * SinPhi
      y2 = Abs(n * SinPhi + nn * CosPhi)
      If y2 <= (Thick + 0.5)
        Application =  0.5 + Thick - y2
        If Application > 1
          Application = 1
        EndIf
        If (x2 > -1) And (x2 < (Hypo + 1))
          If x2 < 0
            Application * (1 + x2)
          ElseIf x2 > Hypo
            Application * (1 - x2 + Hypo)
          EndIf
        Else
          Application = 0
        EndIf
        If Application > 0
          If Application < 1
            Color_Found = Point(X + n * SignX, Y + nn * SignY)
            Plot(X + n * SignX, Y + nn * SignY, g2D_AA_BlendColor(Color, Color_Found, Application))
          Else
            Plot(X + n * SignX, Y + nn * SignY, Color)
          EndIf
        EndIf
      EndIf
    Next nn
  Next n
EndProcedure

Code: Select all

    ;// in Procedure SetMeterState() //
    ; draw the needle
    Protected.i NeedleStyle = 3
    Select NeedleStyle
    Case 1  ; draw needle with a single anti-aliased thick line.
      Circle(x, y, 2, \needleColor)
      g2d_LineXY(\mid_X, \height - 10, x, y, \needleColor, 5)
    Case 2  ; draw outlined needle with anti-aliased lines.
      nw.i = 2 + Round(1.35 - Cos(position), #PB_Round_Nearest)
      Circle(x, y, 2, \needleColor)
      g2d_LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
      g2d_LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
    Case 3  ; draw filled needle with multiple anti-aliased calls.
      nw.i = 2 + Round(1.35 - Cos(position), #PB_Round_Nearest)
      Circle(x, y, 2, \needleColor)
      LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
      LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
      LineXY(\mid_X - 4, \height - 10, \mid_X + 4, \height - 10, \needleColor)
      ; Call FillArea() before color edges are blurred with g2d_LineXY()
      FillArea(\mid_X, \height - 11, \needleColor, \needleColor ! 1)
      g2d_LineXY(\mid_X - 4, \height - 10, x-nw, y, \needleColor)
      g2d_LineXY(\mid_X + 4, \height - 10, x+nw, y, \needleColor)
    EndSelect
The nice thing about standards is there are so many to choose from. ~ Andrew Tanenbaum
User avatar
thinkitsimple
User
User
Posts: 89
Joined: Mon Aug 13, 2012 6:12 pm
Location: Berlin, Germany
Contact:

Re: MeterGadget.pbi, create analog style meters for your pro

Post by thinkitsimple »

Very nice, this will come in handy.

Thanks for sharing.
Michael

PureBasic 5.51, macOS 10.12.2 Sierra
Simo_na
Enthusiast
Enthusiast
Posts: 177
Joined: Sun Mar 03, 2013 9:01 am

Re: MeterGadget.pbi, create analog style meters for your pro

Post by Simo_na »

Real-Time example.
This code get the input amplitude of your sound card.
Include the "MeterGadget.pbi" V 1.11

Code: Select all


EnableExplicit

IncludeFile"MeterGadget.pbi"

#N=4096
#NM1=#N-1
#ND2=#N>>1
#M=12.33
#WINDOW_WIDTH=190
#WINDOW_HEIGHT=650
#SAMPLE_RATE=44100
#gadText1=1

Structure MYWAVEFORMATEX
wFormatTag.u 
nChannels.w
nSamplesPerSec.l
nAvgBytesPerSec.l
nBlockAlign.w
wBitsPerSample.w
cbSize.w
EndStructure

Structure SCOPE
channel.b
left.i
top.i
width.i
height.i
middleY.i
quarterY.i
EndStructure

Structure CONFIG
hWindow.i   
size.i    
buffer.i    
output.i
wave.i
format.MYWAVEFORMATEX
lBuf.i    
nBuf.i    
nDev.i    
nBit.i
nHertz.i    
nChannel.i
LScope.SCOPE
RScope.SCOPE
ScopeTimer.l
EndStructure

Define flags=#PB_Window_ScreenCentered | #PB_Window_MinimizeGadget | #PB_Window_SystemMenu
OpenWindow(0,0,0,640,426,"Real-Time Example",flags)

flags=#MTR_STYLE_VU | #MTR_STYLE_LED

SetMeterNeedleColor(RGB($57,$3B,$23))
SetMeterBottomColor(RGB($4A,$1A,$00))
SetMeterScaleColor(RGB($30,$30,$30))
setMeterGradientColor(RGB ($FF,$D4,$63),RGB ($EE,$A2,$4A),RGB ($AD,$73,$43), RGB ($6E,$54,$3B))
SetMeterCaptionColor(RGB($30,$30,$30))

Define mtr_VU_L=MeterGadget(#PB_Any,0,0,640,"(0 = --13 dBfs) - (+3 = --10 dBfs) - (+2 = --5 dBfs) - (+3 = 0 dBfs)" ,flags)
Define amp_L.f=70.01
SetMeterSmoothing (mtr_VU_L,8)

Global my_ll = mtr_VU_L
SetMeterSmoothing (my_ll,8)

Global Dim rex.f(#N+1),Dim imx.f(#N+1),Dim OutPutArray.f(#N+1)
Global Config.CONFIG,i,Dim inHdr.WAVEHDR(16),*hWave.WAVEHDR,i2
Global LE,LE2,JM1,IP,media1,my_dbfs,ffs,FFTWnd,ddb,lop1,my_log

Config\format\wFormatTag=#WAVE_FORMAT_PCM

Procedure Record_Start()
Config\format\nChannels=2
Config\format\wBitsPerSample=16
Config\format\nSamplesPerSec=#SAMPLE_RATE
Config\nDev=0 
Config\lBuf=#N
Config\nBuf=16
Config\nBit=1
Config\format\nBlockAlign=(Config\format\nChannels*Config\format\wBitsPerSample)/8
Config\format\nAvgBytesPerSec=Config\format\nSamplesPerSec*Config\format\nBlockAlign
   
If #MMSYSERR_NOERROR=waveInOpen_(@Config\wave,#WAVE_MAPPER+Config\nDev,@Config\format,Config\hWindow,#Null,#CALLBACK_WINDOW | #WAVE_FORMAT_DIRECT)
For i=0 To Config\nBuf-1
inHdr(i)\lpData=AllocateMemory(Config\lBuf)
inHdr(i)\dwBufferLength=Config\lBuf
waveInPrepareHeader_(Config\wave,inHdr(i),SizeOf(WAVEHDR))
waveInAddBuffer_(Config\wave,inHdr(i),SizeOf(WAVEHDR))
Next

If #MMSYSERR_NOERROR=waveInStart_(Config\wave)
SetTimer_(Config\hWindow,0,Config\ScopeTimer,0)
EndIf
EndIf

EndProcedure

Procedure Record_Read(hWaveIn.i,lpWaveHdr.i)
*hWave.WAVEHDR=lpWaveHdr
Config\buffer=*hWave\lpData
Config\size=*hWave\dwBytesRecorded
waveInAddBuffer_(hWaveIn,lpWaveHdr,SizeOf(WAVEHDR))
EndProcedure

Procedure record_doFFT(*scope.SCOPE)

Define.d TR,TI,SR,SI,UR,UI
Define.i J,K,L,cnt,MaxValue
Define.w value

J=#ND2

If Config\buffer=0
MessageRequester("Error","No buffer available.")
StopDrawing()
For i=0 To Config\nBuf-1
FreeMemory(inHdr(i)\lpData) 
Next
End
EndIf

For i=0 To Config\size Step 2 
i2=i>>1
rex(i2)=0
imx(i2)=0    
rex(i2+#N>>1)=0
imx(i2+#N>>1)=0
value.w=PeekW(Config\buffer+i+*scope\channel*2) 
rex(i2)=(value/44100)
Next

For i.i=1 To #N-2
If i<J
TR=REX(J)
TI=IMX(J)
REX(J)=REX(i)
IMX(J)=IMX(i)
REX(i)=TR
IMX(i)=TI
EndIf
 
K=#ND2
 
While K<=J
J=J-K
K=K>>1  
Wend
J=J+K
Next

For L=1 To #M 
LE.i=1<<L
LE2.i=LE>>1
UR=1
UI=0
SR=Cos(#PI/LE2)   
SI=-Sin(#PI/LE2)
For J.i=1 To LE2
JM1.i=J-1
For i=JM1 To #NM1
IP.i=i+LE2
TR=REX(IP)*UR-IMX(IP)*UI 
TI=REX(IP)*UI+IMX(IP)*UR
REX(IP)=REX(i)-TR
IMX(IP)=IMX(i)-TI
REX(i)=REX(i)+TR
IMX(i)=IMX(i)+TI
i+LE-1
Next i
TR=UR
UR=TR*SR-UI*SI
UI=TR*SI+UI*SR
Next
Next

For cnt=0 To #N 
outputarray(cnt)=(IMX(cnt)*IMX(cnt))+(REX(cnt)*REX(cnt))
Next

SortArray(outputarray(),#PB_Sort_Descending)

media1=(outputarray(0))

my_log=(20*Log10(media1/44100*4096)*1.081)

SetMeterState(my_ll,my_log)

EndProcedure

Procedure record_CallBack(hWnd.i,Msg.i,wParam.i,lParam.i)
If Msg=#MM_WIM_DATA
record_Read (wParam,lParam)
record_doFFT (Config\LScope)
EndIf
ProcedureReturn #PB_ProcessPureBasicEvents
EndProcedure

Config\hWindow=WindowID(0)
Config\output=WindowOutput(0)

SetWindowCallback(@record_CallBack())
Record_Start()

Repeat
my_ll = mtr_VU_L
Until WaitWindowEvent(25)=#PB_Event_CloseWindow   

For i=0 To Config\nBuf-1
FreeMemory(inHdr(i)\lpData) 
Next

End

User avatar
BasicallyPure
Enthusiast
Enthusiast
Posts: 539
Joined: Thu Mar 24, 2011 12:40 am
Location: Iowa, USA

Re: MeterGadget.pbi, create analog style meters for your pro

Post by BasicallyPure »

Thanks skywalk,
I have revised the code to allow the option to draw needles using anti-aliased lines.
Use the optional flags when the meter gadget is created to select the drawing style you want.
Because the anti-aliase lines increase CPU load the default needle drawing style is my original method.

@Simo_na
I think you are creating a problem by applying a non-linear signal to a non-linear scale.
A real mechanical meter gives a linear needle response regardless of the type of scale behind the needle.
If your input signal is already scaled for dB then the meter scale should have evenly spaced markings.
You can make the input signal non-linear or the meter scale non-linear but not both together.
The VU style meter scale expects a linear input signal.
If the input signal value is one half of full scale then the dB is calculate like this.
dB = 20 * Log10(0.5)
the result is -6 dB.
So if full scale is +3 then half scale is +3 dB subtract 6 dB gives -3 dB.
This is the value shown at half scale on the VU scale.

BP
BasicallyPure
Until you know everything you know nothing, all you have is what you believe.
Post Reply