Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

LED vu Meter User Control

0.00/5 (No votes)
18 Jan 2005 1  
An LED vu Meter Windows User Control in C#.

Four vuMeters in freeze-frame action...

Introduction

While researching a small project to develop round 3D buttons, I came across the very excellent Bob Powell site. Here, I learned all about the PathGradientBrush. After a considerable amount of brain-wringing, I managed to get to grips with the basics of Matrix and Transform. Having thought about writing my own TrackBar control, but discovering that someone else had already done it, I decided to use my new-found knowledge and try to create an LED vu Meter, such as is found in PC and Mac music recording systems. As is often the case, it was easier than I had first thought. It doesn't do anything at all with Digital Signal Processing, so apologies to anyone who was hoping it would, but I think it looks really nice, so it became the subject of my first submission to The Code Project.

The Code

The control inherits from System.Windows.Forms.UserControl, and overrides the OnPaint method. In short, we size each LED so that fifteen will fit nicely within the size of the control, build a 3D border round them, and light them with a PathGradientBrush according to the Volume property.

There are fifteen LEDs, each of which uses three colors. Color 1 is the Surround Color for the "unlit" state, Color 2 is the Centre Color for the "unlit" state AND the Surround Color for the "lit" state, and Color 3 is the Centre Color for the "lit" state. I set these colors up in the ledColours array.

// Array of LED colours  Unlit surround      Lit surround    Lit centre

//                                           Unlit centre

Color[] ledColours = 
           new Color[] {Color.DarkRed,       Color.Red,      Color.White,
                        Color.DarkRed,       Color.Red,      Color.White,
                        Color.DarkRed,       Color.Red,      Color.White,
                        Color.DarkGoldenrod, Color.Orange,   Color.White,
                        Color.DarkGoldenrod, Color.Orange,   Color.White,
                        Color.DarkGoldenrod, Color.Orange,   Color.White,
                        Color.DarkGoldenrod, Color.Orange,   Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White,
                        Color.DarkGreen,     Color.Green,    Color.White};

This may not be the most elegant implementation, but it works, and makes changing the colors simple, if a little tedious. The more observant reader will no doubt notice that the "Lit Centre" is always white, and I could have reduced the size of the array and simply referred to Color.White in the code, but this way, it makes it possible for someone to change the "lit" centre color, if white doesn't work for their chosen surround colors.

The DrawLeds method first calculates the size of each LED, depending on the size of the control. The height is calculated so that fifteen LEDs will fit, with a two-pixel gap between each one.

// Rectangle values for each individual LED - fit them nicely inside the border

int ledLeft = this.ClientRectangle.Left + 3;
int ledTop = this.ClientRectangle.Top + 3;
int ledWidth = this.ClientRectangle.Width - 6;
int ledHeight = this.ClientRectangle.Height / ledCount -2 ;

// Create the LED rectangle

Rectangle ledRect = new Rectangle(ledLeft, ledTop, ledWidth, ledHeight);

Next, we create our GraphicsPath, add our ledRect rectangle to it, and create a PathGradientBrush to use with it.

GraphicsPath gp = new GraphicsPath();               // Create Graphics Path

gp.AddRectangle(ledRect);                           // Add the rectangle

PathGradientBrush pgb = new PathGradientBrush(gp);  // Nice brush for shiny LEDs

We then loop through each LED. The loop has both ascending and descending indices, as explained in the code comments. For each LED, we determine if it's lit or not by comparing the value of j with the value of ledVal (set by the "Volume" property). If our LED is less than or equal to the volume, we light it. (We also light it if it is equal to the peakVal variable, more of which later.) Once we know if we're lighting this LED or not, we select the "Unlit surround" and "Unlit Centre", or "Lit Surround" and "Lit Centre" colors from the ledColours array. This is achieved by simple calculation based on the value of i.

// Light the LED if it's under current value, or if it's the peak value.

if ((j <= ledVal) | (j == peakVal))
{
    pgb.CenterColor=ledColours[i*3+2];
    pgb.SurroundColors=new Color[]{ledColours[i*3+1]};
}
// Otherwise, do not light it

else
{
    pgb.CenterColor=ledColours[i*3+1];
    pgb.SurroundColors=new Color[]{ledColours[i*3]};
}

// Light LED fom the centre.

pgb.CenterPoint=new PointF(ledRect.X + ledRect.Width/2, 
                         ledRect.Y + ledRect.Height/2);

Now for the groovy part. We create a Matrix and use its Translate method to effectively change the value of ledRect.X depending on the value of i. The Transform property of our Graphics instance is then set to our Matrix, mx, to move the current LED down the screen. I worked out how to do this with help from Mahesh Shand here. We then fill the LED in with our trusty PathGradientBrush.

// Use a matrix to move the LED graphics down according to the value of i

Matrix mx = new Matrix();
mx.Translate(0, i * (ledHeight + 2));
g.Transform=mx;
g.FillRectangle(pgb, ledRect);

Once we've drawn all our LEDs, we have to reset the Graphics instance to its original offset, so we can draw the border in the right place.

// Translate back to original position to draw the border

Matrix mx1 = new Matrix();
mx1.Translate(0, 0);
g.Transform = mx1;

The best bit, I thought, was implementing a Peak Level Indicator. This means that the LED at the highest level reached stays lit for a certain amount of time. This is achieved as follows.

First of all, this piece of code in the Volume property determines if the latest value is higher than the previously saved peak value. If so, it saves it in peakVal. Also, the timer is started.

// New peak value

if (ledVal > peakVal)
{
    peakVal = ledVal;
    timer1.Enabled = true;        // Tell the peak indicator to stay

}

The peak value will stay set either until it is exceeded, or until the timer interval elapses, at which point it will be reset to zero, and the LED will go out.

private void timer1_Tick(object sender, EventArgs e)
{
    timer1.Enabled = false;       // Tell the peak indicator to go

    peakVal = 0;                  //

    this.Invalidate();
}

Using the code

It's pretty simple to use - the sample application shows how. If you include vuMeterLED.dll in your project, you should be able to drag the control onto your form. If you download the source, note that I've only included the vuMeterLED.cs, Form1.cs and AssemblyInfo.cs files. This is because I developed the code using SharpDevelop, and I don't know if the project files would be readable by VS.

That's about it then! Thanks for reading this far, and I hope it's useful or educational to someone.

What's next?

Well, the control could do with a scale, which shouldn't be too difficult to draw. More of a challenge would be developing an analog style meter with a moving needle. I'm thinking about it...

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here