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   
GeneralMy vote of 5memberAlexB4715 Jun '11 - 23:02 
Good work..
Generali want to convert it into WPFmembervishal_h18 May '11 - 23:21 
i want to create same application in WPF then what i have to do..can any one tell me..i used image control for picture box..but its giving me error like .....
Connvert from 'ref System.Windows.Controls.Image' to 'ref System.Drawing.Image'
please any one can give me some input..
GeneralProb with the frequency domainmemberjoeblogg6 Jan '11 - 8:03 
Hi. Great prog.
I'm having trouble with the frequency domain display. At first it didn't display anything, then I changed the pen color to red instead of it being amplitude-dependent and it was then sort of realistic-looking, but it hardly changes, and certinly doesn't change with the frequency. I'm using my voice into a microphone - not very musical, but definitely high & low are different!, and I've also tried it with a PC signal generator from the speakers into the mike.
The time domain looks good.
I've tried using the FourierTransform.cs from SoundCatcher instead (and of course used FFT, not FFTDb), but it's still the same.
Any idea?
Thanks,
GeneralRe: Prob with the frequency domainmemberPiligrim200729 Jan '11 - 5:31 
Look at this. It will solve the problem.
http://www.codeproject.com/Messages/3241610/Error-in-Program.aspx
QuestionHow to get the frequency in real-time?groupgmajkun22 Dec '10 - 19:54 
Thx for you to provide this project. Now i want use it to get the frequency in real-time,but i don't know how to do it?Please tell me.Thx again.
AnswerRe: How to get the frequency in real-time?memberPiligrim200729 Jan '11 - 5:37 
Just:
- take the FFT array;
- find the maximum value and the index of this value in array (do not process '0'-index, such as it is ZeroFrequency coefficient);
 
The gotten value gives you idea about how to get the real frequency.
GeneralMy vote of 5memberDennis Betten3 Nov '10 - 1:41 
Pretty good basis for building a vu Led Meter. Thanx!
GeneralErrormemberdreje20105 Oct '10 - 23:53 
Hi Jeff,
I tried to turn everything in VB
and seems to be ok ... but when running I get an error here:
 
Friend Class WaveInHelper
Public Shared Sub [Try] (ByVal err As Integer)
If Err <> Then WaveNative.MMSYSERR_NOERROR
Throw New Exception (err.ToString ())
End If
End Sub
End Class
 
err is passed = 11... Why?
Any suggestions?
 

Thanks in advance
bye
my mail
andrea_pc07@alice.it
QuestionDoes this analyse the code with in milisecond accurancy?memberRahdu15 Sep '10 - 9:50 
I need a real time sound analysis. I tried implementing my own solution for this problem but i keep getting a latency from 30 to 250 millisecond. I am trying to implement it on windows mobile 7. get all the peaks in a second (i implemented a FFT agorithm for this).I am new o sound analysis my first project on this stuff sow any help will be greatly appreciated.
GeneralDetect Peak on single channelmembersurvcopt5 Mar '10 - 3:47 
Hi great code
Actually I don't understand all the code Smile | :) .
I try to simply detect a peak on single channel every second .
You code is too harder for me D'Oh! | :doh: . Is there as simple way to make an interrupt each time the code detect a peak ?
I hope you can help me
Thanks
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
GeneralRe: Media filemembergerardo_00115 Mar '10 - 8:15 
I found that you can "record the speakers" by opening the Volume Control, go to the Options menu, select Properties. If you click on the Recording radio button, the available devices have usually unchecked the option Rec. Playback.
Check that box and ypur soud recorder will catch whatever you are playing.
QuestionWhere is the Documentation?memberMember 603819619 Mar '09 - 7:37 
this an Awesome program you have.. "Kudos"
 
but I really want to understand how you code work!
I am reading your code line by line to understand put it taking too long and I get confused midway
 
I am searching for any documentation you have .. what you posted is not enough.
 
can you help me out ... I appreciate your help
QuestionaudioFrameSize in waveformat correct ?memberradio_gaga8 Mar '09 - 8:16 
Hi,
 
thanks for your example code.
 
Can it be that the first argument in the WaveFormat constructor is the audioSamplesPerSecond instead of the audioFrameSize. At least the member names of WaveFormat suggest it.
 
See :
 
Form1:
private void Start()
{
Stop();
try
{
__waveFormat = new WaveFormat(_audioFrameSize, _audioBitsPerSample, _audioChannels);
AnswerRe: audioFrameSize in waveformat correct ?memberJeff Morton8 Mar '09 - 9:59 
I corrected a number of errors with the subsequent updated version of this demo called SoundCatcher.
 
http://www.codeproject.com/KB/audio-video/SoundCatcher.aspx[^]
 
Use this instead.
GeneralIt won't work on desktop PCmembermax_99014 Feb '09 - 0:15 
Hi Jeff!
 
Recently I face a problem when running your project on my friend's desktop PC.
The program can't pick up any voice wave from the mic of the desktop PC.
But, your project work perfectly on my laptop.
Do you know what is the cause of this problem?

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

Permalink | Advertise | Privacy | Mobile
Web01 | 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