Majickal Majickal - 1 year ago 260
C# Question

How to draw an audio waveform to a bitmap

I am attempting to extract the audio content of a wav file and export the resultant waveform as an image (bmp/jpg/png).

So I have found the following code which draws a sine wave and works as expected:

string filename = @"C:\0\test.bmp";
int width = 640;
int height = 480;
Bitmap b = new Bitmap(width, height);

for (int i = 0; i < width; i++)
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, Color.Black);

This works completely as expected, what I would like to do is replace

int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);

with something like

int y = converted and scaled float from monoWaveFileFloatValues

So how would I best go about doing this in the simplest manner possible?

I have 2 basic issues I need to deal with (i think)

  1. convert float to int in a way which does not loose information, this is due to
    SetPixel(i, y, Color.Black);
    where x & y are both int

  2. sample skipping on the x axis so the waveform fits into the defined space
    audio length / image width
    give the number of samples to average out intensity over which would be represented by a single pixel

The other options is find another method of plotting the waveform which does not rely on the method noted above. Using a chart might be a good method, but I would like to be able to render the image directly if possible

This is all to be run from a console application and I have the audio data (minus the header) already in a float array.


The following code enabled me to draw the required output using
but it took about 30 seconds to process 27776 samples and whilst it does do what I need, it is far too slow to be useful. So I am still looking towards a solution which will draw the bitmap directly.

System.Windows.Forms.DataVisualization.Charting.Chart chart = new System.Windows.Forms.DataVisualization.Charting.Chart();
chart.Size = new System.Drawing.Size(640, 320);

// Plot {sin(x), 0, 2pi}
chart.Series["sin"].LegendText = args[0];
chart.Series["sin"].ChartType = System.Windows.Forms.DataVisualization.Charting.SeriesChartType.Spline;

//for (double x = 0; x < 2 * Math.PI; x += 0.01)
for (int x = 0; x < audioDataLength; x ++)
//chart.Series["sin"].Points.AddXY(x, Math.Sin(x));
chart.Series["sin"].Points.AddXY(x, leftChannel[x]);

// Save sin_0_2pi.png image file
chart.SaveImage(@"c:\tmp\example.png", System.Drawing.Imaging.ImageFormat.Png);

Output shown below:
enter image description here

Answer Source

So I managed to figure it out using a code sample found here, though I made some minor changes to the way I interact with it.

public static Bitmap DrawNormalizedAudio(List<float> data, Color foreColor, Color backColor, Size imageSize, string imageFilename)
    Bitmap bmp = new Bitmap(imageSize.Width, imageSize.Height);

    int BORDER_WIDTH = 0;
    float width = bmp.Width - (2 * BORDER_WIDTH);
    float height = bmp.Height - (2 * BORDER_WIDTH);

    using (Graphics g = Graphics.FromImage(bmp))
        Pen pen = new Pen(foreColor);
        float size = data.Count;
        for (float iPixel = 0; iPixel < width; iPixel += 1)
            // determine start and end points within WAV
            int start = (int)(iPixel * (size / width));
            int end = (int)((iPixel + 1) * (size / width));
            if (end > data.Count)
                end = data.Count;

            float posAvg, negAvg;
            averages(data, start, end, out posAvg, out negAvg);

            float yMax = BORDER_WIDTH + height - ((posAvg + 1) * .5f * height);
            float yMin = BORDER_WIDTH + height - ((negAvg + 1) * .5f * height);

            g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax, iPixel + BORDER_WIDTH, yMin);
    return null;

private static void averages(List<float> data, int startIndex, int endIndex, out float posAvg, out float negAvg)
    posAvg = 0.0f;
    negAvg = 0.0f;

    int posCount = 0, negCount = 0;

    for (int i = startIndex; i < endIndex; i++)
        if (data[i] > 0)
            posAvg += data[i];
            negAvg += data[i];

    if (posCount > 0)
       posAvg /= posCount;
    if (negCount > 0)
       negAvg /= negCount;

In order to get it working I had to do a couple of things prior to calling the method DrawNormalizedAudio you can see below what I needed to do:

    Size imageSize = new Size();
    imageSize.Width = 1000;
    imageSize.Height = 500;
    List<float> lst = leftChannel.OfType<float>().ToList(); //change float array to float list - see link below
    DrawNormalizedAudio(lst, Color.Red, Color.White, imageSize, @"c:\tmp\example2.png");

* change float array to float list

The result of this is as follows, a waveform representation of a hand clap wav sample: enter image description here

I am quite sure there needs to be some updates/revisions to the code, but it's a start and hopefully this will assist someone else who is trying to do the same thing I was.

If you can see any improvements that can be made, let me know.


  1. NaN issue mentioned in the comments now resolved and code above updated.
  2. Waveform Image updated to represent output fixed by removal of NaN values as noted in point 1.


Average level (not RMS) was determined by summing the max level for each sample point and dividing by the total number of samples. Examples of this can be seen below:

Silent Wav File: enter image description here

Hand Clap Wav File: enter image description here

Brownian, Pink & White Noise Wav File: enter image description here

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download