Click here to Skip to main content
15,886,137 members
Articles / Programming Languages / C#

Visualizing Sound

Rate me:
Please Sign up or sign in to vote.
4.94/5 (24 votes)
6 Nov 2012CPOL7 min read 77.1K   7.7K   67  
Listen or playback sound and visualize the frequency spread.
// -----------------------------------------------------------------------
// <copyright file="SoundVisualizer.cs" company="None.">
//  By Philip R. Braica (HoshiKata@aol.com, VeryMadSci@gmail.com)
//
//  Distributed under the The Code Project Open License (CPOL)
//  http://www.codeproject.com/info/cpol10.aspx
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace SoundFiltering
{
    /// <summary>
    /// Sound visualizer.
    /// </summary>
    public partial class SoundVisualizer : UserControl
    {
        #region protected items.

        /// <summary>
        /// Close flag.
        /// </summary>
        protected bool m_close = false;

        /// <summary>
        /// Back buffer.
        /// </summary>
        protected Bitmap m_back = null;

        /// <summary>
        /// Front buffer.
        /// </summary>
        protected Bitmap m_front = null;

        /// <summary>
        /// List of stacks.
        /// </summary>
        protected List<Stack> m_stack = new List<Stack>();

        /// <summary>
        /// Lock object.
        /// </summary>
        protected object m_lockObject = new object();

        /// <summary>
        /// Minimum for color scaling.
        /// </summary>
        protected float m_min = 0;

        /// <summary>
        /// Maximum for color scaling.
        /// </summary>
        protected float m_max = 0;

        /// <summary>
        /// To do list.
        /// </summary>
        protected List<float[]> m_toDoBuffers = new List<float[]>();

        /// <summary>
        /// Thread lock.
        /// </summary>
        protected object m_threadLock = new object();

        /// <summary>
        /// Work thread.
        /// </summary>
        protected System.Threading.Thread m_workThread = null;
        #endregion

        #region Public Interface: Constructor, TriggerRedraw, AppendData, SpectralWidth, ShowFFT

        /// <summary>
        /// Trigger redraw.
        /// </summary>
        public void TriggerRedraw()
        {
            timer1.Enabled = true;
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public SoundVisualizer()
        {
            InitializeComponent();
            pictureBox1.Resize += new EventHandler(pictureBox1_Resize);
            this.Disposed += new EventHandler(SoundVisualizer_Disposed);
            ShowFFT = true;
            SpectralWidth = 256;
        }

        /// <summary>
        /// Append data.
        /// </summary>
        /// <param name="data"></param>
        public void AppendData(float[] data)
        {
            lock (m_threadLock)
            {
                m_toDoBuffers.Add(data);
                if (m_workThread == null)
                {
                    m_workThread = new System.Threading.Thread(new System.Threading.ThreadStart(WorkLoop));
                    m_workThread.Name = "Sound visualization work thread.";
                    m_workThread.Start();
                }
            }
        }

        /// <summary>
        /// Spectral width.
        /// </summary>
        public int SpectralWidth { get; set; }

        /// <summary>
        /// Show FFT means display FFT data, other wise show time domain.
        /// </summary>
        public bool ShowFFT { get; set; }

        #endregion

        #region Non-public functions.
        /// <summary>
        /// Disposed
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void SoundVisualizer_Disposed(object sender, EventArgs e)
        {
            m_close = true;
        }

        /// <summary>
        /// Resize event.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void pictureBox1_Resize(object sender, EventArgs e)
        {
            timer1.Enabled = true;
        }

        /// <summary>
        /// Resize buffers as needed.
        /// </summary>
        protected void resizeBuffers()
        {
            int w = pictureBox1.Width;
            int h = SpectralWidth/2; // pictureBox1.Height;
            w = w < 10 ? 10 : w;
            h = h < 10 ? 10 : h;
            System.Drawing.Bitmap old = m_back;
            if (old == null)
            {
                m_back = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            }
            else
            {
                if ((m_back.Width != w) || (m_back.Height != h))
                {
                    m_back = new Bitmap(w, h, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
                }
            }
            if (m_back != old)
            {
                if (old != null)
                {
                    old.Dispose();
                }
            }
        }

        /// <summary>
        /// Swap buffers.
        /// </summary>
        protected void swapBuffers()
        {
            System.Drawing.Bitmap tmp = m_front;
            m_front = m_back;
            m_back = tmp;
        }

        /// <summary>
        /// Timer 1.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void timer1_Tick(object sender, EventArgs e)
        {
            doRedraw();
        }
        /// <summary>
        /// Do redraw.
        /// </summary>
        protected void doRedraw()
        {
            resizeBuffers();
            using (System.Drawing.Graphics g = System.Drawing.Graphics.FromImage(m_back))
            {
                paintGraph(g, m_back, m_back.Width, m_back.Height);
            }
            swapBuffers();
            pictureBox1.Image = m_front;
        }

        /// <summary>
        /// Stack class.
        /// </summary>
        protected class Stack
        {      
            /// <summary>
            /// Color map.
            /// </summary>
            protected static Color[] clrMap;

            /// <summary>
            /// Gamma corrected.
            /// </summary>
            protected static int []gamma = new int[1000];

            /// <summary>
            /// Make the color map.
            /// </summary>
            static Stack()
            {

                Color[] crs = {
                Color.Black, Color.Blue, Color.Green,
                Color.GreenYellow, Color.Yellow, Color.Orange, Color.OrangeRed, Color.Red};
                clrMap = new Color[crs.Length * 100];
                int k = 0;
                for (int i = 0; i < crs.Length - 1; i++)
                {
                    for (int j = 0; j < 100; j++)
                    {
                        Color low = crs[i];
                        Color high = crs[i + 1];
                        int r = ((low.R * (100 - j)) + (high.R * j)) / 100;
                        int g = ((low.G * (100 - j)) + (high.G * j)) / 100;
                        int b = ((low.B * (100 - j)) + (high.B * j)) / 100;
                        clrMap[k++] = Color.FromArgb(r, g, b);
                    }
                }
                float gammaCorrection = 0.3f;
                float gain = (float)((clrMap.Length - 1) / System.Math.Pow(gamma.Length, gammaCorrection));
                for (int i = 0; i < gamma.Length; i++)
                {
                    int val = (int)(gain * System.Math.Pow((double)i, gammaCorrection));
                    val = val < 0 ? 0 : val > clrMap.Length - 1 ? clrMap.Length - 1 : val;
                    gamma[i] = val;
                }
            }

            /// <summary>
            /// Constructor.
            /// </summary>
            public Stack(float[] vals, double min, double max)
            {
                float scale = (float)(gamma.Length / (max - min));
                Colors = new Color[vals.Length / 2];
                for (int i = 0; i < Colors.Length; i++)
                {
                    int v = (int)((vals[i] - min) * scale);
                    v = v < 0 ? 0 : v > clrMap.Length - 1 ? clrMap.Length - 1 : v;
                    v = gamma[v];
                    Colors[i] = clrMap[v];
                }
            }

            /// <summary>
            /// Colors array.
            /// </summary>
            public Color[] Colors = null;
        }

        /// <summary>
        /// WorkThread.
        /// </summary>
        protected void WorkLoop()
        {
            float[] lastBuf = null;
            while (m_close == false)
            {
                System.Threading.Thread.Sleep(100);
                float[] buf = null;
                lock (m_threadLock)
                {
                    if (m_toDoBuffers.Count > 0)
                    {
                        buf = m_toDoBuffers[0];
                        m_toDoBuffers.RemoveAt(0);
                    }
                }
                if (buf != null)
                {
                    if (lastBuf != null)
                    {
                        int sw = SpectralWidth;
                        // doing 1/8th overlaps, so sew last buffer - 1/8th plus the next buffer - 1/8th
                        float[] tmp = new float[(int)(sw * 1.75f)];
                        int k = 0; 
                        int swo = sw - (sw>>3);
                        int startLastBuf = lastBuf.Length - swo;
                        startLastBuf = startLastBuf < 0 ? 0 : startLastBuf;
                        for (int i = startLastBuf; i < lastBuf.Length; i++)
                        {
                            tmp[k++] = lastBuf[i];
                        }
                      
                        for (int i = 0; i < swo && k < tmp.Length && i < buf.Length; i++)
                        {
                            tmp[k++] = buf[i];
                        }
                        workOnBuffer(tmp);

                    }
                    workOnBuffer(buf);
                    
                    lastBuf = buf;
                }

            }
        }

        /// <summary>
        /// Work on the buffer.
        /// </summary>
        /// <param name="data"></param>
        protected void workOnBuffer(float[] data)
        {
            List<Stack> tmp = new List<Stack>();

            int k = 0;
            float[] da = new float[SpectralWidth];
            float[] fft = new float[da.Length];
            int nextk = 0;
            while (k <= data.Length - da.Length)
            {
                nextk = k + (da.Length >> 3);
                for (int i = 0; i < da.Length; i++)
                {
                    da[i] = data[k++];
                }
                Math.FFT.Forward(da, fft);
                float[] outp = ShowFFT ? fft : da;
                float min = outp[0];
                float max = outp[0];
              
                for (int i = 0; i < outp.Length; i++)
                {
                    min = min < outp[i] ? min : outp[i];
                    max = max > outp[i] ? max : outp[i];
                }
                min = float.IsNaN(min) ? m_min : min;
                max = float.IsNaN(max) ? m_max : max;
                min = float.IsInfinity(min) ? m_min : min;
                max = float.IsInfinity(max) ? m_max : max;
                m_min = (255 * m_min + min) / 256;
                m_max = (255 * m_max + max) / 256;
                tmp.Add(new Stack(outp, m_min, m_max));
                k = nextk;
            }
            lock (m_lockObject)
            {
                m_stack.AddRange(tmp);
            }
        }
       
        /// <summary>
        /// Paint.
        /// </summary>
        /// <param name="g"></param>
        /// <param name="width"></param>
        /// <param name="height"></param>
        protected void paintGraph(Graphics g, Bitmap b, int width, int height)
        {
            List<Stack> tmp = new List<Stack>();
            lock (m_lockObject)
            {
                if (m_stack.Count > 1)
                {
                    tmp.AddRange(m_stack);
                    m_stack.Clear();
                }
            }
            
            if (m_front == null)
            {
                g.Clear(Color.Black);
            }
            else
            {

                g.DrawImage(m_front, new Point(tmp.Count, 0));
                if (tmp.Count < 1)
                {
                    return;
                }
            }
            for (int i = 0; i < tmp.Count; i++)
            {
                int hh = tmp[0].Colors.Length; 
                for (int j = 0; j < height; j++)
                {
                    b.SetPixel(i, hh - j - 1, tmp[tmp.Count - 1 - i].Colors[j]);
                }
            }
        }
        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
United States United States
Phil is a Principal Software developer focusing on weird yet practical algorithms that run the gamut of embedded and desktop (PID loops, Kalman filters, FFTs, client-server SOAP bindings, ASIC design, communication protocols, game engines, robotics).

In his personal life he is a part time mad scientist, full time dad, and studies small circle jujitsu, plays guitar and piano.

Comments and Discussions