|
// -----------------------------------------------------------------------
// <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.
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.