5
  1. Is there any audio/programming-related stack-exchange site?
  2. I'm trying to make a wave form in WinForms
  3. What algorithm should I use?

For example, if I have 200 samples per pixel (vertical line), should I draw the lowest and the highest sample from that portion of 200 samples? Or should I draw average of low and high samples? Maybe both in different colors?

Jason Sundram
  • 12,225
  • 19
  • 71
  • 86
apocalypse
  • 5,764
  • 9
  • 47
  • 95
  • There isn't much of an algorithm behind it. You got numbers, the samples from the audio file. Connect the dots with a line. A polyline works best. Colors and scaling are completely up to your taste. – Hans Passant Jul 12 '12 at 12:36
  • Yes, but it only works when you use very big zoom. If you have lots of samples per pixel, you need to choose different solution. – apocalypse Jul 12 '12 at 12:53
  • Why not just sample the samples - pick one for every pixel? If you're zoomed out far enough to have 200 samples represented by a single pixel, I'm not sure how useful it is to know the minimum and maximum values in the range. That's what zooming in is for. – anton.burger Jul 12 '12 at 13:15
  • If I use min/max sample I can easy see highest/lowest values which may be an errors of analog recording etc. If I use samples as samples I will not see anything interesting. If I use average all tracks will looks similar. – apocalypse Jul 12 '12 at 13:36
  • 2
    This has been asked before and answered here. Here's an answer I gave. http://stackoverflow.com/questions/11091924/drawing-waveform-converting-to-db-squashes-it/11104418 Try searching some more. – Bjorn Roche Jul 12 '12 at 18:10

5 Answers5

9

This will help you to generate waveform from audio file using nAudio in C#...

using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class test : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
    string strPath = Server.MapPath("audio/060.mp3");
    string SongID = "2";
    byte[] bytes = File.ReadAllBytes(strPath);
    WriteToFile(SongID,strPath, bytes);
    Response.Redirect("Main.aspx");
    }

private void WriteToFile(string SongID, string strPath, byte[] Buffer)
{
    try
    {
        int samplesPerPixel = 128;
        long startPosition = 0;
        //FileStream newFile = new FileStream(GeneralUtils.Get_SongFilePath() + "/" + strPath, FileMode.Create);
        float[] data = FloatArrayFromByteArray(Buffer);

        Bitmap bmp = new Bitmap(1170, 200);

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

        NAudio.Wave.Mp3FileReader reader = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
        NAudio.Wave.WaveChannel32 channelStream = new NAudio.Wave.WaveChannel32(reader);

        int bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * channelStream.WaveFormat.Channels;

        using (Graphics g = Graphics.FromImage(bmp))
        {

            g.Clear(Color.White);
            Pen pen1 = new Pen(Color.Gray);
            int size = data.Length;

            string hexValue1 = "#009adf";
            Color colour1 = System.Drawing.ColorTranslator.FromHtml(hexValue1);
            pen1.Color = colour1;

            Stream wavestream = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));

            wavestream.Position = 0;
            int bytesRead1;
            byte[] waveData1 = new byte[samplesPerPixel * bytesPerSample];
            wavestream.Position = startPosition + (width * bytesPerSample * samplesPerPixel);

            for (float x = 0; x < width; x++)
            {
                short low = 0;
                short high = 0;
                bytesRead1 = wavestream.Read(waveData1, 0, samplesPerPixel * bytesPerSample);
                if (bytesRead1 == 0)
                    break;
                for (int n = 0; n < bytesRead1; n += 2)
                {
                    short sample = BitConverter.ToInt16(waveData1, n);
                    if (sample < low) low = sample;
                    if (sample > high) high = sample;
                }
                float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
                float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
                float lowValue = height * lowPercent;
                float highValue = height * highPercent;
                g.DrawLine(pen1, x, lowValue, x, highValue);

            }
        }

        string filename = Server.MapPath("image/060.png");
        bmp.Save(filename);
        bmp.Dispose();

    }
catch (Exception e)
    {

    }
}
public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
{
    return FloatArrayFromByteArray(stream.GetBuffer());
}

public float[] FloatArrayFromByteArray(byte[] input)
{
    float[] output = new float[input.Length / 4];
    for (int i = 0; i < output.Length; i++)
    {
        output[i] = BitConverter.ToSingle(input, i * 4);
    }
    return output;
}

}
Illaya
  • 666
  • 6
  • 14
  • 3
    Thanks, but this question is a bit old :) I did finish my app already. http://i.imgur.com/DfAkGrv.png – apocalypse Nov 25 '13 at 11:02
  • Hey @zgnilec I need your help. I am also trying same as you developed. Can you pls help me to complete it? I am done only waveform generation. Also I need to do zoom features and moving line on waveform. Help me. – Illaya Nov 25 '13 at 11:42
  • Then contact me via email. I will answer later, because I'm in work now. – apocalypse Nov 25 '13 at 12:00
  • multiverse at inbox dot com. Send all your questions. – apocalypse Nov 25 '13 at 12:18
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/41870/discussion-between-illaya-and-zgnilec) – Illaya Nov 25 '13 at 12:51
  • Hey @apocalypse, I know it’s been 7 years since you posted your screen shot, but is that source code around still? I’m having issues with scroll and zoom and would like to see your maths. Mine is ok, but it’s out by about 1% per minute. Cheers. – vr_driver Aug 12 '20 at 15:22
  • @vr_driver: Source code is not available. But maths are very simple. Here you have some different aproaches to draw waveform: https://github.com/naudio/NAudio.WaveFormRenderer/tree/master/WaveFormRendererLib – apocalypse Aug 21 '20 at 15:47
  • All good now. Fortunately I did manage to get my maths correct in the end. Thanks for the tip. – vr_driver Aug 22 '20 at 00:09
6
  1. Try dsp.stackexchange.com

  2. At 200 samples per pixel, there are several approaches you can try. Whatever you do, it often works best to draw each vertical line both above and below 0, ie. treat positive and negative sample values seperately. Probably the easiest is to just calculate an RMS. At such a low resolution peak values will probably give you a misleading representation of the waveform.

Redeye
  • 1,582
  • 1
  • 14
  • 22
1

You can use AudioControl from code project.

and see this one: Generating various audio waveforms in C#

these projects may be useful for you if implement your code originally:

Ria
  • 10,237
  • 3
  • 33
  • 60
  • That project is incomplete, and author says it sometimes causing errors. I need to build more advanced thing. I tried to compare my output with Audacity wave form and it was looking very similar. However I need some suggestions from stack overflow. – apocalypse Jul 12 '12 at 12:34
1

Incase anyone runs into this:

You can treat the samples per pixel as your zoom level, at higher levels (zoomed out more) you will probably want to subsample that for performance reasons.

You will most likely want a fixed width that fits on the screen to draw on and use virtual scrolling (so you don't potentially have a draw area of several million pixels).

You can calculate the value for each pixel by iterating over the audio data with: skip (scroll position * samples per pixel) + (pixel * samples per pixel) take samples per pixel. This allows for performant infinite zoom and scroll as you only read and draw the minimum amount to fill the view. The scroll width is calculated with audio data length / samples per pixel.

Audio samples are generally shown in one of two ways, the peak value of the sample range or the rms value. The rms value is calculated by summing the squares of all values in the sample range, divide the sum by sample length, the rms value if the squareroot of this (rms will be a bit higher than average and is a good measure of perceived loudness)

You can increase performance in multiple ways such as increasing sub sampling (causes loss of detail), throttling the scroll and making the draw requests cancelable incase new scroll fires before previous is rendered.

David Sherman
  • 320
  • 2
  • 9
  • So when you zoom, you are skipping audio samples, correct? For instance, if you are at 2x zoom, you are reading every even audio sample and skipping the odd ones. There are a number of problems with this approach, such as downsampling at with a factor of 100 doesn't give good results. One approach is to build samples at multiple zoom levels but that again consumes lot of memory and startup time. – Deepak Sharma Mar 09 '22 at 17:39
0

just to document it, if you want to make the audio file fill the width of the output image

samplesPerPixel = (reader.Length / bytesPerSample) / width ;
Moshe L
  • 1,797
  • 14
  • 19