Click here to Skip to main content
Click here to Skip to main content

Sound visualizer in C#

By , 15 Aug 2007
 
Screenshot - SoundViewer.jpg

Introduction

This project demonstrates the use of the Fast Fourier Transform and Windows GDI to produce near real-time visualizations of the time and frequency domains of sound.

Background

While looking for information on signal processing for a robotics project, it became apparent that examples written in C# were hard to find. This led me to create some of my own classes, which I have provided here for this demonstration.

Using the Code

Audio input for this demonstration is provided by the Wave classes developed by Ianier Munoz. Wave samples are further processed using the AudioFrame class.

class AudioFrame
{
    private Bitmap _canvasTimeDomain;
    private Bitmap _canvasFrequencyDomain;
    private double[] _waveLeft;
    private double[] _waveRight;
    private double[] _fftLeft;
    private double[] _fftRight;
    private SignalGenerator _signalGenerator;
    private bool _isTest = false;
    public AudioFrame(bool isTest)
    {
        _isTest = isTest;
    }

    /// <summary>
    /// Process 16 bit sample
    /// </summary>
    /// <param name="wave"></param>
    public void Process(ref byte[] wave)
    {
        _waveLeft = new double[wave.Length / 4];
        _waveRight = new double[wave.Length / 4];
        if (_isTest == false)
        {
            // Split out channels from sample
            int h = 0;
            for (int i = 0; i < wave.Length; i += 4)
            {
                _waveLeft[h] = (double)BitConverter.ToInt16(wave, i);
                _waveRight[h] = (double)BitConverter.ToInt16(wave, i + 2);
                h++;
            }
        }
        else
        {
            // Generate artificial sample for testing
            _signalGenerator = new SignalGenerator();
            _signalGenerator.SetWaveform("Sine");
            _signalGenerator.SetSamplingRate(44100);
            _signalGenerator.SetSamples(16384);
            _signalGenerator.SetFrequency(5000);
            _signalGenerator.SetAmplitude(32768);
            _waveLeft = _signalGenerator.GenerateSignal();
            _waveRight = _signalGenerator.GenerateSignal();
        }
        // Generate frequency domain data in decibels
        _fftLeft = FourierTransform.FFTDb(ref _waveLeft);
        _fftRight = FourierTransform.FFTDb(ref _waveRight);
    }

    /// <summary>
    /// Render time domain to PictureBox
    /// </summary>
    /// <param name="pictureBox"></param>
    public void RenderTimeDomain(ref PictureBox pictureBox)
    {
        // Set up for drawing
        _canvasTimeDomain = new Bitmap(pictureBox.Width, pictureBox.Height);
        Graphics offScreenDC = Graphics.FromImage(_canvasTimeDomain);
        SolidBrush brush = new System.Drawing.SolidBrush
                    (Color.FromArgb(0, 0, 0));
        Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);
        // Determine channel boundaries
        int width = _canvasTimeDomain.Width;
        int center = _canvasTimeDomain.Height / 2;
        int height = _canvasTimeDomain.Height;
        offScreenDC.DrawLine(pen, 0, center, width, center);
        int leftLeft = 0;
        int leftTop = 0;
        int leftRight = width;
        int leftBottom = center - 1;
        int rightLeft = 0;
        int rightTop = center + 1;
        int rightRight = width;
        int rightBottom = height;
        // Draw left channel
        double yCenterLeft = (leftBottom - leftTop) / 2;
        double yScaleLeft = 0.5 * (leftBottom - leftTop) / 32768;  
            // a 16 bit sample has values from -32768 to 32767
        int xPrevLeft = 0, yPrevLeft = 0;
        for (int xAxis = leftLeft; xAxis < leftRight; xAxis++)
        {
            int yAxis = (int)(yCenterLeft + (_waveLeft[_waveLeft.Length / 
                (leftRight - leftLeft) * xAxis] * yScaleLeft));
            if (xAxis == 0)
            {
                xPrevLeft = 0;
                yPrevLeft = yAxis;
            }
            else
            {
                pen.Color = Color.LimeGreen;
                offScreenDC.DrawLine(pen, xPrevLeft, yPrevLeft, xAxis,yAxis);
                xPrevLeft = xAxis;
                yPrevLeft = yAxis;
            }
        }
        // Draw right channel
        int xCenterRight = rightTop + ((rightBottom - rightTop) / 2);
        double yScaleRight = 0.5 * (rightBottom - rightTop) / 32768;  
            // a 16 bit sample has values from -32768 to 32767
        int xPrevRight = 0, yPrevRight = 0;
        for (int xAxis = rightLeft; xAxis < rightRight; xAxis++)
        {
            int yAxis = (int)(xCenterRight + (_waveRight[_waveRight.Length / 
            (rightRight - rightLeft) * xAxis] * yScaleRight));
            if (xAxis == 0)
            {
                xPrevRight = 0;
                yPrevRight = yAxis;
            }
            else
            {
                pen.Color = Color.LimeGreen;
                offScreenDC.DrawLine
                (pen, xPrevRight, yPrevRight, xAxis, yAxis);
                xPrevRight = xAxis;
                yPrevRight = yAxis;
            }
        }
        // Clean up
        pictureBox.Image = _canvasTimeDomain;
        offScreenDC.Dispose();
    }

    /// <summary>
    /// Render frequency domain to PictureBox
    /// </summary>
    /// <param name="pictureBox"></param>
    public void RenderFrequencyDomain(ref PictureBox pictureBox)
    {
        // Set up for drawing
        _canvasFrequencyDomain = new Bitmap
                (pictureBox.Width, pictureBox.Height);
        Graphics offScreenDC = Graphics.FromImage(_canvasFrequencyDomain);
        SolidBrush brush = new System.Drawing.SolidBrush
                    (Color.FromArgb(0, 0, 0));
        Pen pen = new System.Drawing.Pen(Color.WhiteSmoke);
        // Determine channel boundaries
        int width = _canvasFrequencyDomain.Width;
        int center = _canvasFrequencyDomain.Height / 2;
        int height = _canvasFrequencyDomain.Height;
        offScreenDC.DrawLine(pen, 0, center, width, center);
        int leftLeft = 0;
        int leftTop = 0;
        int leftRight = width;
        int leftBottom = center - 1;
        int rightLeft = 0;
        int rightTop = center + 1;
        int rightRight = width;
        int rightBottom = height;
        // Draw left channel
        for (int xAxis = leftLeft; xAxis < leftRight; xAxis++)
        {
            double amplitude = (int)_fftLeft[(int)(((double)(_fftLeft.Length)
                         / (double)(width)) * xAxis)];
            if (amplitude < 0) // Drop negative values
            amplitude = 0;
            int yAxis = (int)(leftTop + 
                ((leftBottom - leftTop) * amplitude) / 100);  
                        // Arbitrary factor
            pen.Color = Color.FromArgb(0, 0, (int)amplitude % 255);
            offScreenDC.DrawLine(pen, xAxis, leftTop, xAxis, yAxis);
        }
        // Draw right channel
        for (int xAxis = rightLeft; xAxis < rightRight; xAxis++)
        {
            double amplitude = (int)_fftRight[(int)(((double)
            (_fftRight.Length) / (double)(width)) * xAxis)];
            if (amplitude < 0)
            amplitude = 0;
            int yAxis = (int)(rightBottom - 
            ((rightBottom - rightTop) * amplitude) / 100);
            pen.Color = Color.FromArgb(0, 0, (int)amplitude % 255);
            offScreenDC.DrawLine(pen, xAxis, rightBottom, xAxis, yAxis);
        }
        // Clean up
        pictureBox.Image = _canvasFrequencyDomain;
        offScreenDC.Dispose();
    }
}

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)

About the Author

Jeff Morton
Systems / Hardware Administrator
United States United States
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
Questionhow can I determine the sound level without waveFormat ?memberfasialkk24 Feb '10 - 13:44 
how can I determine the sound level while the sound is recording in order to use the sound degree value in another applications ?
 
thanx alot
QuestionHow To Open Recorded Wav FilememberTV Mogul14 Jan '10 - 2:38 
Hi,
 
Nice work!
 
How would you open an existing wav file and display the graph using Peak values?
 
I want to open a wav file and draw its graph with zoom capability for an audio editor?
 
Bill
 
http://www.KabbalahCode.com

Generalimportant reguestmemberseka2087 Dec '09 - 22:04 
after greeting
iwant to catch on .dll or source code
to generate sound map from image
for using it in project for blind person
my email:seka208@yahoo.com
thanks for you
QuestionDoes not work in Windows 7 (64bit) Why?membera.mueller2 Dec '09 - 5:31 
System.NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
bei SoundViewer.WaveNative.waveInOpen(IntPtr& phwi, Int32 uDeviceID, WaveFormat lpFormat, WaveDelegate dwCallback, Int32 dwInstance, Int32 dwFlags)
bei SoundViewer.WaveInRecorder..ctor(Int32 device, WaveFormat format, Int32 bufferSize, Int32 bufferCount, BufferDoneEventHandler doneProc)
bei SoundViewer.Form1.Start()
AnswerRe: Does not work in Windows 7 (64bit) Why?membereweewf15 Feb '10 - 8:42 
64-Bit should be the first clue. :P
 
Didn't work on my Windows Server 2008, 64-Bit either. Then I thought that winmm.dll must be a 32-Bit dll. As a result, any application using 32-bit DLL files needs to change it's configuration, or exceptions will occur...
 
Visual Studio 2008
 
Click the "Build" -> "Configuration Manager..." menu option. Click the "Active solution platform" drop-down menu and select "New...". A dialog will open, change the type to "x86", click "OK", and rebuild. Smile | :)
QuestionIs this available in VB.NET?memberSimon Williamson2 Apr '09 - 1:21 
Is this available in VB.NET?
GeneralMedia filememberBabgi26 Mar '09 - 9:19 
Hi Jeff..This works great when I use microphone but how can i input the sound from a media file being played by a media player instead of a microphone.I would like to see the visualization of a song being played. Please let me know.
Thanks
GeneralRe: Media filemembergamosemalaka30 May '09 - 22:00 
Yeah I also need information about that! Sniff | :^)
GeneralRe: Media filememberdiogopms27 Jun '09 - 15:37 
I need this modification please..
I need to compare two sounds (wav or mp3 songs)
 
Anyone know how to do that??
GeneralRe: Media filememberAnkur Dukare11 Mar '10 - 22:34 
Hi Jeff,
 

For displaying the waveform for an audio stream arriving from the Microphone, what you are doing is calling the "waveInOpen()" method for the Microphone device. This ope the microphone device.
 
Is there some way you can open the "Speakers" and then instead of re-directing the audio-stream coming from Microphone (as you have in this sample code) to the sine-wave display, you re-direct the audio stream going towards Speakers to the sine-wave display.
 
This way, you can show the wave-form for a file being played in the media player.
 

Thanks,
Anky

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130523.1 | Last Updated 15 Aug 2007
Article Copyright 2007 by Jeff Morton
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid