728x90
반응형
728x170
▶ FFT.cs
using System;
namespace TestProject
{
/// <summary>
/// 고속 푸리엔 변환
/// </summary>
public class FFT
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 사인 배정도 실수 배열
/// </summary>
/// <remarks>sin(pi / 2), sin(pi / 4), sin(pi / 8) ...</remarks>
protected static double[] _sineDoubleArray = null;
/// <summary>
/// 사인 단정도 실수 배열
/// </summary>
protected static float[] _sineFloatArray = null;
/// <summary>
/// 코사인 배정도 실수 배열
/// </summary>
/// <remarks>cos(pi / 2), cos(pi / 4), cos(pi / 8) ...</remarks>
protected static double[] _cosineDoubleArray = null;
/// <summary>
/// 코사인 단정도 실수 배열
/// </summary>
protected static float[] _cosineFloatArray = null;
/// <summary>
/// 테이블 잠금 객체
/// </summary>
protected static object _tableLock = new object();
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
// 배정도 실수
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
public static void ComputeForwardFFT(double[] sourceRealArray)
{
ComputeFFT(sourceRealArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, targetRealArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
public static void ComputeForwardFFT(double[] sourceRealArray, double[] targetRealArray)
{
ComputeFFT(sourceRealArray, targetRealArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeForwardFFT(double[] sourceRealArray, double[] targetRealArray, double[] targetImaginaryArray)
{
ComputeFFT(sourceRealArray, targetRealArray, targetImaginaryArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, sourceImaginaryArray, targetRealArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
public static void ComputeForwardFFT(float[] sourceRealArray, float[] sourceImaginaryArray, float[] targetRealArray)
{
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeForwardFFT
(
double[] sourceRealArray,
double[] sourceImaginaryArray,
double[] targetRealArray,
double[] targetImaginaryArray
)
{
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, true);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
public static void ComputeInverseFFT(double[] sourceRealArray)
{
ComputeFFT(sourceRealArray, false);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, targetRealArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
public static void ComputeInverseFFT(double[] sourceRealArray, double[] targetRealArray)
{
ComputeFFT(sourceRealArray, targetRealArray, false);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeInverseFFT(double[] sourceRealArray, double[] targetRealArray, double[] targetImaginaryArray)
{
ComputeFFT(sourceRealArray, targetRealArray, targetImaginaryArray, true);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeInverseFFT
(
double[] sourceRealArray,
double[] sourceImaginaryArray,
double[] targetRealArray,
double[] targetImaginaryArray
)
{
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, false);
}
#endregion
// 단정도 실수
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
public static void ComputeForwardFFT(float[] sourceRealArray)
{
ComputeFFT(sourceRealArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, targetRealArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
public static void ComputeForwardFFT(float[] sourceRealArray, float[] targetRealArray)
{
ComputeFFT(sourceRealArray, targetRealArray, true);
}
#endregion
#region 순방향 FFT 계산하기 - ComputeForwardFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 순방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeForwardFFT
(
float[] sourceRealArray,
float[] sourceImaginaryArray,
float[] targetRealArray,
float[] targetImaginaryArray
)
{
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, true);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
public static void ComputeInverseFFT(float[] sourceRealArray)
{
ComputeFFT(sourceRealArray, false);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, targetRealArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
public static void ComputeInverseFFT(float[] sourceRealArray, float[] targetRealArray)
{
ComputeFFT(sourceRealArray, targetRealArray, false);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeInverseFFT(float[] sourceRealArray, float[] targetRealArray, float[] targetImaginaryArray)
{
ComputeFFT(sourceRealArray, targetRealArray, targetImaginaryArray, true);
}
#endregion
#region 역방향 FFT 계산하기 - ComputeInverseFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray)
/// <summary>
/// 역방향 FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
public static void ComputeInverseFFT
(
float[] sourceRealArray,
float[] sourceImaginaryArray,
float[] targetRealArray,
float[] targetImaginaryArray
)
{
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, false);
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Protected
#region 배열 값 설정하기 - SetArrayValue()
/// <summary>
/// 배열 값 설정하기
/// </summary>
protected static void SetArrayValue()
{
if(_sineDoubleArray != null)
{
return;
}
lock(_tableLock)
{
if(_sineDoubleArray != null)
{
return;
}
int size = 20;
double[] sineDoubleArray = new double[size];
float[] sineFloatArray = new float[size];
double[] cosineDoubleArray = new double[size];
float[] cosineFloatArray = new float[size];
double pi = Math.PI;
for(int i = 0; i < size; i++)
{
sineDoubleArray[i] = Math.Sin(pi);
sineFloatArray[i] = (float)(sineDoubleArray[i]);
cosineDoubleArray[i] = Math.Cos(pi);
cosineFloatArray[i] = (float)(cosineDoubleArray[i]);
pi /= 2;
}
_sineFloatArray = sineFloatArray;
_sineDoubleArray = sineDoubleArray;
_cosineFloatArray = cosineFloatArray;
_cosineDoubleArray = cosineDoubleArray;
}
}
#endregion
#region 비트 반전하기 - ReverseBit(value, bitCount)
/// <summary>
/// 비트 반전하기
/// </summary>
/// <param name="value">값</param>
/// <param name="bitCount">비트 수</param>
/// <returns>반전 값</returns>
protected static int ReverseBit(int value, int bitCount)
{
int result = 0;
for(int b = 0; b < bitCount; ++b)
{
result <<= 1;
result |= value & 1;
value >>= 1;
}
return result;
}
#endregion
// 배정도 실수
#region FFT 수행하기 - PerformFFT(sourceRealArray, sourceImaginaryArray, forward)
/// <summary>
/// FFT 수행하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void PerformFFT(double[] sourceRealArray, double[] sourceImaginaryArray, bool forward)
{
int length = sourceRealArray.Length;
double sign = forward ? 1 : -1;
int index = 0;
for(int p = 1; p < length; p <<= 1)
{
int p2 = p << 1;
double sine = sign * _sineDoubleArray[index];
double cosine = _cosineDoubleArray[index++];
double wr = 1.0;
double wi = 0.0;
int j;
for(j = 0; j < p; ++j)
{
int k;
for(k = j; k < length; k += p2)
{
int k2 = k + p;
double tr = (wr * sourceRealArray[k2]) - (wi * sourceImaginaryArray[k2]);
double ti = (wr * sourceImaginaryArray[k2]) + (wi * sourceRealArray[k2]);
sourceRealArray[k2] = sourceRealArray[k] - tr;
sourceImaginaryArray[k2] = sourceImaginaryArray[k] - ti;
sourceRealArray[k] += tr;
sourceImaginaryArray[k] += ti;
}
double nwr = (wr * cosine) - (wi * sine);
double nwi = (wi * cosine) + (wr * sine);
wr = nwr;
wi = nwi;
}
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT
(
double[] sourceRealArray,
double[] sourceImaginaryArray,
double[] targetRealArray,
double[] targetImaginaryArray,
bool forward
)
{
SetArrayValue();
int length = sourceRealArray.Length;
int i;
int bitCount = 0;
for(i = 1; i < length; i <<= 1)
{
bitCount++;
}
for(i = 0; i < length; i++)
{
int p = ReverseBit(i, bitCount);
targetRealArray[p] = sourceRealArray[i];
targetImaginaryArray[p] = sourceImaginaryArray[i];
}
PerformFFT(targetRealArray, targetImaginaryArray, forward);
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(double[] sourceRealArray, bool forward)
{
double[] targetRealArray = new double[sourceRealArray.Length];
double[] sourceImaginaryArray = new double[sourceRealArray.Length];
double[] targetImaginaryArray = new double[sourceRealArray.Length];
for(int i = 0; i < sourceRealArray.Length; i++)
{
sourceImaginaryArray[i] = 0;
}
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward);
for(int i = 0; i < sourceRealArray.Length; i++)
{
targetRealArray[i] = System.Math.Sqrt
(
(targetRealArray[i] * targetRealArray[i]) + (targetImaginaryArray[i] * targetImaginaryArray[i])
);
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, targetRealArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(double[] sourceRealArray, double[] targetRealArray, bool forward)
{
double[] sourceImaginaryArray = new double[sourceRealArray.Length];
double[] targetImaginaryArray = new double[sourceRealArray.Length];
for(int i = 0; i < sourceRealArray.Length; i++)
{
sourceImaginaryArray[i] = 0;
}
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward);
for(int i = 0; i < sourceRealArray.Length; i++)
{
targetRealArray[i] = Math.Sqrt
(
(targetRealArray[i] * targetRealArray[i]) + (targetImaginaryArray[i] * targetImaginaryArray[i])
);
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, targetRealArray, targetImaginaryArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(double[] sourceRealArray, double[] targetRealArray, double[] targetImaginaryArray, bool forward)
{
double[] sourceImaginaryArray = new double[sourceRealArray.Length];
for(int i = 0; i < sourceRealArray.Length; i++)
{
sourceImaginaryArray[i] = 0;
}
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward);
}
#endregion
// 단정도 실수
#region FFT 수행하기 - PerformFFT(sourceRealArray, sourceImaginaryArray, forward)
/// <summary>
/// FFT 수행하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void PerformFFT(float[] sourceRealArray, float[] sourceImaginaryArray, bool forward)
{
int length = sourceRealArray.Length;
float sign = forward ? 1 : -1;
int index = 0;
for(int p = 1; p < length; p <<= 1)
{
int p2 = p << 1;
float sine = sign * _sineFloatArray[index];
float cosine = _cosineFloatArray[index++];
float wr = 1.0f;
float wi = 0.0f;
int j;
for(j = 0; j < p; j++)
{
int k;
for(k = j; k < length; k += p2)
{
int k2 = k + p;
float tr = (wr * sourceRealArray[k2]) - (wi * sourceImaginaryArray[k2]);
float ti = (wr * sourceImaginaryArray[k2]) + (wi * sourceRealArray[k2]);
sourceRealArray[k2] = sourceRealArray[k] - tr;
sourceImaginaryArray[k2] = sourceImaginaryArray[k] - ti;
sourceRealArray[k] += tr;
sourceImaginaryArray[k] += ti;
}
float nwr = (wr * cosine) - (wi * sine);
float nwi = (wi * cosine) + (wr * sine);
wr = nwr;
wi = nwi;
}
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소수 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="targetImaginaryArray">타겟 허수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT
(
float[] sourceRealArray,
float[] sourceImaginaryArray,
float[] targetRealArray,
float[] targetImaginaryArray,
bool forward
)
{
SetArrayValue();
int length = sourceRealArray.Length;
int i;
int bitCount = 0;
for(i = 1; i < length; i <<= 1)
{
bitCount++;
}
length = 1 << (bitCount-1);
for(i = 0; i < length; i++)
{
int p = ReverseBit(i, bitCount);
targetRealArray[p] = sourceRealArray[i];
targetImaginaryArray[p] = sourceImaginaryArray[i];
}
PerformFFT(targetRealArray, targetImaginaryArray, forward);
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(float[] sourceRealArray, bool forward)
{
float[] sourceImaginaryArray = new float[sourceRealArray.Length];
float[] targetRealArray = new float[sourceRealArray.Length];
float[] targetImaginaryArray = new float[sourceRealArray.Length];
for(int i = 0; i < sourceRealArray.Length; i++)
{
sourceImaginaryArray[i] = 0;
}
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward);
for(int i = 0; i < sourceRealArray.Length; i++)
{
targetRealArray[i] = (float)Math.Sqrt
(
(targetRealArray[i] * targetRealArray[i]) + (targetImaginaryArray[i] * targetImaginaryArray[i])
);
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, targetRealArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(float[] sourceRealArray, float[] targetRealArray, bool forward)
{
float[] sourceImaginaryArray = new float[sourceRealArray.Length];
float[] targetImaginaryArray = new float[sourceRealArray.Length];
float[] temporaryRealArray = new float[sourceRealArray.Length];
for(int i = 0; i < sourceRealArray.Length; i++)
{
sourceImaginaryArray[i] = 0;
}
ComputeFFT(sourceRealArray, sourceImaginaryArray, temporaryRealArray, targetImaginaryArray, forward);
for(int i = 0; i < targetRealArray.Length; i++)
{
targetRealArray[i] = (float)Math.Sqrt
(
(temporaryRealArray[i] * temporaryRealArray[i]) + (targetImaginaryArray[i] * targetImaginaryArray[i])
);
}
}
#endregion
#region FFT 계산하기 - ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, forward)
/// <summary>
/// FFT 계산하기
/// </summary>
/// <param name="sourceRealArray">소스 실수 배열</param>
/// <param name="sourceImaginaryArray">소스 허수 배열</param>
/// <param name="targetRealArray">타겟 실수 배열</param>
/// <param name="forward">순방향 여부</param>
protected static void ComputeFFT(float[] sourceRealArray, float[] sourceImaginaryArray, float[] targetRealArray, bool forward)
{
float[] targetImaginaryArray = new float[sourceRealArray.Length];
ComputeFFT(sourceRealArray, sourceImaginaryArray, targetRealArray, targetImaginaryArray, forward);
for(int i = 0; i < sourceRealArray.Length; i++)
{
targetRealArray[i] = (float)Math.Sqrt
(
(targetRealArray[i] * targetRealArray[i]) + (targetImaginaryArray[i] * targetImaginaryArray[i])
);
}
}
#endregion
}
}
반응형
▶ Line.cs
using System.Drawing;
namespace TestProject
{
/// <summary>
/// 선
/// </summary>
public class Line
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 명칭
/// </summary>
public string Name = string.Empty;
/// <summary>
/// 표시 여부
/// </summary>
public bool Visible = true;
/// <summary>
/// X 배열
/// </summary>
/// <remarks>NULL인 경우 Y 인덱스를 사용한다.</remarks>
public double[] XArray = null;
/// <summary>
/// Y 배열
/// </summary>
public double[] YArray = null;
/// <summary>
/// 선 색상
/// </summary>
public Color LineColor = Color.Black;
/// <summary>
/// 선 너비
/// </summary>
public float LineWidth = 1f;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 최소 X - MinimumX
/// <summary>
/// 최소 X
/// </summary>
public double MinimumX
{
get
{
double [] xArray = XArray;
if(xArray == null)
{
return 0;
}
if(xArray.Length < 1)
{
return 0;
}
double minimumX = xArray[0];
for(int i = 0; i < xArray.Length; i++)
{
minimumX = minimumX < xArray[i] ? minimumX : xArray[i];
}
return minimumX;
}
}
#endregion
#region 최대 X - MaximumX
/// <summary>
/// 최대 X
/// </summary>
public double MaximumX
{
get
{
double[] xArray = XArray;
if(xArray == null)
{
double[] yArray = YArray;
if(yArray == null)
{
return 0;
}
return yArray.Length;
}
if(xArray.Length < 1)
{
return 0;
}
double maximumX = xArray[0];
for(int i = 0; i < xArray.Length; i++)
{
maximumX = maximumX > xArray[i] ? maximumX : xArray[i];
}
return maximumX;
}
}
#endregion
#region 최소 X - MinimumY
/// <summary>
/// 최소 X
/// </summary>
public double MinimumY
{
get
{
double[] yArray = YArray;
if(yArray == null)
{
return 0;
}
if(yArray.Length < 1)
{
return 0;
}
double minimumY = yArray[0];
for(int i = 0; i < yArray.Length; i++)
{
minimumY = minimumY < yArray[i] ? minimumY : yArray[i];
}
return minimumY;
}
}
#endregion
#region 최대 Y - MaximumY
/// <summary>
/// 최대 Y
/// </summary>
public double MaximumY
{
get
{
double[] yArray = YArray;
if(yArray == null)
{
return 0;
}
if(yArray.Length < 1)
{
return 0;
}
double maximumY = yArray[0];
for(int i = 0; i < yArray.Length; i++)
{
maximumY = maximumY > yArray[i] ? maximumY : yArray[i];
}
return maximumY;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 업데이트하기 - Update(xArray, yArray)
/// <summary>
/// 업데이트하기
/// </summary>
/// <param name="xArray">X 배열</param>
/// <param name="yArray">Y 배열</param>
public void Update(float[] xArray, float[] yArray)
{
if(xArray != null)
{
bool resize = XArray == null ? true : XArray.Length == xArray.Length ? false : true;
double[] newXArray = resize ? new double[xArray.Length] : XArray;
for(int i = 0; i < xArray.Length; i++)
{
newXArray[i] = xArray[i];
}
XArray = newXArray;
}
else
{
XArray = null;
}
if(yArray != null)
{
bool resize = YArray == null ? true : YArray.Length == yArray.Length ? false : true;
double[] newYArray = resize ? new double[yArray.Length] : YArray;
for(int i = 0; i < yArray.Length; i++)
{
newYArray[i] = yArray[i];
}
YArray = newYArray;
}
else
{
YArray = null;
}
}
#endregion
#region 업데이트하기 - Update(yArray)
/// <summary>
/// 업데이트하기
/// </summary>
/// <param name="yArray">Y 배열</param>
public void Update(float[] yArray)
{
Update(null, yArray);
}
#endregion
#region 업데이트하기 - Update(xArray, yArray)
/// <summary>
/// 업데이트하기
/// </summary>
/// <param name="xArray">X 배열</param>
/// <param name="yArray">Y 배열</param>
public void Update(double[] xArray, double[] yArray)
{
if(xArray != null)
{
bool resize = XArray == null ? true : XArray.Length == xArray.Length ? false : true;
double[] newXArray = resize ? new double[xArray.Length] : XArray;
for(int i = 0; i < xArray.Length; i++)
{
newXArray[i] = xArray[i];
}
XArray = newXArray;
}
else
{
XArray = null;
}
if(yArray != null)
{
bool resize = YArray == null ? true : YArray.Length == yArray.Length ? false : true;
double[] newYArray = resize ? new double[yArray.Length] : YArray;
for(int i = 0; i < yArray.Length; i++)
{
newYArray[i] = yArray[i];
}
YArray = newYArray;
}
else
{
YArray = null;
}
}
#endregion
#region 업데이트하기 - Update(yArray)
/// <summary>
/// 업데이트하기
/// </summary>
/// <param name="yArray">Y 배열</param>
public void Update(double[] yArray)
{
Update(null, yArray);
}
#endregion
#region 그리기 - Draw(graphics, reactangle, lowerX, upperX, lowerY, upperY)
/// <summary>
/// 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="reactangle">사각형</param>
/// <param name="lowerX">하한 X</param>
/// <param name="upperX">상한 X</param>
/// <param name="lowerY">하한 Y</param>
/// <param name="upperY">상한 Y</param>
public void Draw(Graphics graphics, Rectangle reactangle, double lowerX, double upperX, double lowerY, double upperY)
{
double[] xArray = XArray;
double[] yArray = YArray;
if(yArray == null)
{
return;
}
int length = xArray == null ? yArray.Length : yArray.Length > xArray.Length ? xArray.Length : yArray.Length;
double endX = 0;
double endY = 0;
upperX = upperX == lowerX ? lowerX + 1 : upperX;
upperY = upperY == lowerY ? lowerY + 1 : upperY;
double xScale = reactangle.Width / (upperX - lowerX);
double yScale = reactangle.Height / (upperY - lowerY);
using(Pen pen = new Pen(LineColor, LineWidth))
{
for(int i = 0; i < length; i++)
{
double startX = xArray == null ? i : xArray[i];
double startY = yArray[i];
startX = reactangle.X + ((startX - lowerX) * xScale);
startY = reactangle.Y + reactangle.Height - ((startY - lowerY) * yScale);
if(i != 0)
{
graphics.DrawLine(pen, (float)startX, (float)startY, (float)endX, (float)endY);
}
endX = startX;
endY = startY;
}
}
}
#endregion
}
}
728x90
▶ GraphControl.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 그래프 컨트롤
/// </summary>
public partial class GraphControl : UserControl
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 텍스트 색상
/// </summary>
public Color TextColor = Color.Black;
/// <summary>
/// 선 리스트
/// </summary>
public List<Line> LineList = new List<Line>();
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 후면 버퍼 비트맵
/// </summary>
protected Bitmap backBufferBitmap = null;
/// <summary>
/// 전면 버퍼 비트맵
/// </summary>
protected Bitmap frontBufferBitmap = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 레전드 표시 여부 - ShowLegend
/// <summary>
/// 레전드 표시 여부
/// </summary>
public bool ShowLegend { get; set; }
#endregion
#region 축 표시 여부 - ShowAxis
/// <summary>
/// 축 표시 여부
/// </summary>
public bool ShowAxis { get; set; }
#endregion
#region 그리드 표시 여부 - ShowGrid
/// <summary>
/// 그리드 표시 여부
/// </summary>
public bool ShowGrid { get; set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - GraphControl()
/// <summary>
/// 생성자
/// </summary>
public GraphControl()
{
InitializeComponent();
ShowLegend = true;
ShowAxis = true;
ShowGrid = true;
this.timer.Tick += timer_Tick;
this.pictureBox.Resize += pictureBox_Resize;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 타이머 시작하기 - StartTimer()
/// <summary>
/// 타이머 시작하기
/// </summary>
public void StartTimer()
{
this.timer.Enabled = true;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 버퍼 크기 변경하기 - ResizeBuffer()
/// <summary>
/// 버퍼 크기 변경하기
/// </summary>
protected void ResizeBuffer()
{
int width = this.pictureBox.Width;
int height = this.pictureBox.Height;
width = width < 10 ? 10 : width;
height = height < 10 ? 10 : height;
Bitmap previousBitmap = this.backBufferBitmap;
if(previousBitmap == null)
{
this.backBufferBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
else
{
if((this.backBufferBitmap.Width != width) || (this.backBufferBitmap.Height != height))
{
this.backBufferBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
}
if(this.backBufferBitmap != previousBitmap)
{
if(previousBitmap != null)
{
previousBitmap.Dispose();
}
}
}
#endregion
#region 레전드 그리기 - DrawLegend(graphics, rectangle, legendList, colorList)
/// <summary>
/// 레전드 그리기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="rectangle">사각형</param>
/// <param name="legendList">레전드 리스트</param>
/// <param name="colorList">색상 리스트</param>
protected void DrawLegend(Graphics graphics, Rectangle rectangle, List<string> legendList, List<Color> colorList)
{
int legentCount = legendList.Count;
int rectangleX = rectangle.X;
for(int i = 0; i < legendList.Count; i++)
{
int rectangleWidth = rectangle.X + (rectangle.Width * (i + 1) / legentCount);
using(SolidBrush fillBrush = new SolidBrush(colorList[i]))
{
graphics.FillRectangle(fillBrush, rectangleX, rectangle.Y, rectangleWidth, rectangle.Height);
}
using(SolidBrush stringBrush = new SolidBrush(colorList[i].GetBrightness() > 0.5 ? Color.Black : Color.White))
{
SizeF legendSize = graphics.MeasureString(legendList[i], Font);
PointF legentPoint = new PointF
(
(float)(0.5 * (rectangleWidth + rectangleX - legendSize.Width)),
(float)(rectangle.Y + 0.5 * (legendSize.Height - legendSize.Height))
);
graphics.DrawString(legendList[i], Font, stringBrush, legentPoint);
}
rectangleX = rectangleWidth;
}
}
#endregion
#region 그래프 칠하기 - PaintGraph(graphics, width, height)
/// <summary>
/// 그래프 칠하기
/// </summary>
/// <param name="graphics">그래픽스</param>
/// <param name="width">너비</param>
/// <param name="height">높이</param>
protected void PaintGraph(Graphics graphics, int width, int height)
{
double upperX = 0d;
double lowerX = 0d;
double upperY = 0d;
double lowerY = 0d;
bool foundFirst = false;
List<string> legendList = new List<string>();
List<Color> colorList = new List<Color>();
for(int i = 0; i < LineList.Count; i++)
{
if(LineList[i] == null)
{
continue;
}
if(LineList[i].Visible == false)
{
continue;
}
if(!foundFirst)
{
upperX = LineList[i].MaximumX;
lowerX = LineList[i].MinimumX;
upperY = LineList[i].MaximumY;
lowerY = LineList[i].MinimumY;
foundFirst = true;
}
legendList.Add(LineList[i].Name );
colorList.Add (LineList[i].LineColor);
upperX = upperX > LineList[i].MaximumX ? upperX : LineList[i].MaximumX;
lowerX = lowerX < LineList[i].MinimumX ? lowerX : LineList[i].MinimumX;
upperY = upperY > LineList[i].MaximumY ? upperY : LineList[i].MaximumY;
lowerY = lowerY < LineList[i].MinimumY ? lowerY : LineList[i].MinimumY;
}
graphics.Clear(BackColor);
Rectangle drawRectangle = new Rectangle(0, 0, width, height);
if(ShowLegend)
{
int legendHeight = 5 + (int)graphics.MeasureString("AjtW", Font).Height;
if(height - legendHeight > 10)
{
drawRectangle = new Rectangle(0, 0, width, height - legendHeight);
Rectangle legendRectangle = new Rectangle(0, height - legendHeight + 2, width, legendHeight - 2);
DrawLegend(graphics, legendRectangle, legendList, colorList);
}
}
if(ShowAxis)
{
// 추후 추가
}
if(!foundFirst)
{
return;
}
for(int i = 0; i < LineList.Count; i++)
{
if(LineList[i] == null)
{
continue;
}
if(LineList[i].Visible == false)
{
continue;
}
LineList[i].Draw(graphics, drawRectangle, lowerX, upperX, lowerY, upperY);
}
}
#endregion
#region 버퍼 교체하기 - SwapBuffer()
/// <summary>
/// 버퍼 교체하기
/// </summary>
protected void SwapBuffer()
{
Bitmap temporaryBitmap = this.frontBufferBitmap;
this.frontBufferBitmap = this.backBufferBitmap;
this.backBufferBitmap = temporaryBitmap;
}
#endregion
#region 그래프 그리기 - DrawGraph()
/// <summary>
/// 그래프 그리기
/// </summary>
protected void DrawGraph()
{
ResizeBuffer();
using(Graphics graphics = Graphics.FromImage(this.backBufferBitmap))
{
PaintGraph(graphics, this.backBufferBitmap.Width, this.backBufferBitmap.Height);
}
SwapBuffer();
this.pictureBox.Image = this.frontBufferBitmap;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 타이머 틱 처리하기 - timer_Tick(sender, e)
/// <summary>
/// 타이머 틱 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void timer_Tick(object sender, EventArgs e)
{
DrawGraph();
}
#endregion
#region 픽처 박스 크기 조정시 처리하기 - pictureBox_Resize(sender, e)
/// <summary>
/// 픽처 박스 크기 조정시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void pictureBox_Resize(object sender, EventArgs e)
{
StartTimer();
}
#endregion
}
}
300x250
▶ Stack.cs
using System;
using System.Drawing;
namespace TestProject
{
/// <summary>
/// 스택
/// </summary>
public class Stack
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 색상 배열
/// </summary>
protected static Color[] _colorArray;
/// <summary>
/// 감마 수정 배열
/// </summary>
protected static int[] _gammaCorrectionArray = new int[1000];
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 색상 배열
/// </summary>
public Color[] ColorArray = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Static
#region 생성자 - Stack()
/// <summary>
/// 생성자
/// </summary>
static Stack()
{
Color[] colorArray =
{
Color.Black,
Color.Blue,
Color.Green,
Color.GreenYellow,
Color.Yellow,
Color.Orange,
Color.OrangeRed,
Color.Red
};
_colorArray = new Color[colorArray.Length * 100];
int k = 0;
for(int i = 0; i < colorArray.Length - 1; i++)
{
for(int j = 0; j < 100; j++)
{
Color lowColor = colorArray[i];
Color highColor = colorArray[i + 1];
int r = ((lowColor.R * (100 - j)) + (highColor.R * j)) / 100;
int g = ((lowColor.G * (100 - j)) + (highColor.G * j)) / 100;
int b = ((lowColor.B * (100 - j)) + (highColor.B * j)) / 100;
_colorArray[k++] = Color.FromArgb(r, g, b);
}
}
float gammaCorrection = 0.3f;
float gain = (float)((_colorArray.Length - 1) / Math.Pow(_gammaCorrectionArray.Length, gammaCorrection));
for(int i = 0; i < _gammaCorrectionArray.Length; i++)
{
int value = (int)(gain * Math.Pow((double)i, gammaCorrection));
value = value < 0 ? 0 : value > _colorArray.Length - 1 ? _colorArray.Length - 1 : value;
_gammaCorrectionArray[i] = value;
}
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - Stack(valueArray, minimumValue, maximumValue)
/// <summary>
/// 생성자
/// </summary>
/// <param name="valueArray">값 배열</param>
/// <param name="maximumValue">최소 값</param>
/// <param name="minimumValue">최대 값</param>
public Stack(float[] valueArray, double minimumValue, double maximumValue)
{
float scale = (float)(_gammaCorrectionArray.Length / (maximumValue - minimumValue));
ColorArray = new Color[valueArray.Length / 2];
for(int i = 0; i < ColorArray.Length; i++)
{
int value = (int)((valueArray[i] - minimumValue) * scale);
value = value < 0 ? 0 : value > _colorArray.Length - 1 ? _colorArray.Length - 1 : value;
value = _gammaCorrectionArray[value];
ColorArray[i] = _colorArray[value];
}
}
#endregion
}
}
▶ SoundVisualizer.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 사운드 시각화기
/// </summary>
public partial class SoundVisualizer : UserControl
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 종료 여부
/// </summary>
protected bool closed = false;
/// <summary>
/// 후면 버퍼 비트맵
/// </summary>
protected Bitmap backBufferBitmap = null;
/// <summary>
/// 전면 버퍼 비트맵
/// </summary>
protected Bitmap frontBufferBitmap = null;
/// <summary>
/// 스택 리스트
/// </summary>
protected List<Stack> stackList = new List<Stack>();
/// <summary>
/// 스택 리스트 잠금 객체
/// </summary>
protected object stackListLockObject = new object();
/// <summary>
/// 최소 값
/// </summary>
protected float minimumValue = 0;
/// <summary>
/// 최대 값
/// </summary>
protected float maximumValue = 0;
/// <summary>
/// 값 배열 리스트
/// </summary>
protected List<float[]> valueArrayList = new List<float[]>();
/// <summary>
/// 스레드 잠금 객체
/// </summary>
protected object threadLockObject = new object();
/// <summary>
/// 작업 스레드
/// </summary>
protected Thread workThread = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 스펙트럼 너비 - SpectralWidth
/// <summary>
/// 스펙트럼 너비
/// </summary>
public int SpectralWidth { get; set; }
#endregion
#region FFT 데이터 표시 여부 - ShowFFT
/// <summary>
/// FFT 데이터 표시 여부
/// </summary>
public bool ShowFFT { get; set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - SoundVisualizer()
/// <summary>
/// 생성자
/// </summary>
public SoundVisualizer()
{
InitializeComponent();
ShowFFT = true;
SpectralWidth = 256;
Disposed += UserControl_Disposed;
this.pictureBox.Resize += pictureBox_Resize;
this.timer.Tick += timer_Tick;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 타이머 시작하기 - StartTimer()
/// <summary>
/// 타이머 시작하기
/// </summary>
public void StartTimer()
{
this.timer.Enabled = true;
}
#endregion
#region 데이터 추가하기 - AppendData(valueArray)
/// <summary>
/// 데이터 추가하기
/// </summary>
/// <param name="valueArray">값</param>
public void AppendData(float[] valueArray)
{
lock(this.threadLockObject)
{
this.valueArrayList.Add(valueArray);
if(this.workThread == null)
{
this.workThread = new Thread(new ThreadStart(ProcessWorkThread));
this.workThread.Name = "Sound visualization work thread.";
this.workThread.Start();
}
}
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
//////////////////////////////////////////////////////////////////////////////// Event
#region 사용자 컨트롤 리소스 해제시 처리하기 - UserControl_Disposed(sender, e)
/// <summary>
/// 사용자 컨트롤 리소스 해제시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
protected void UserControl_Disposed(object sender, EventArgs e)
{
this.closed = true;
}
#endregion
#region 픽처 박스 크기 조정시 처리하기 - pictureBox_Resize(sender, e)
/// <summary>
/// 픽처 박스 크기 조정시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
protected void pictureBox_Resize(object sender, EventArgs e)
{
this.timer.Enabled = true;
}
#endregion
#region 타이머 틱 처리하기 - timer_Tick(object sender, EventArgs e)
/// <summary>
/// 타이머 틱 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void timer_Tick(object sender, EventArgs e)
{
Redraw();
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 버퍼 크기 변경하기 - ResizeBuffer()
/// <summary>
/// 버퍼 크기 변경하기
/// </summary>
protected void ResizeBuffer()
{
int width = this.pictureBox.Width;
int height = SpectralWidth / 2;
width = width < 10 ? 10 : width;
height = height < 10 ? 10 : height;
Bitmap previousButmap = this.backBufferBitmap;
if(previousButmap == null)
{
this.backBufferBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
else
{
if((this.backBufferBitmap.Width != width) || (this.backBufferBitmap.Height != height))
{
this.backBufferBitmap = new Bitmap(width, height, PixelFormat.Format32bppArgb);
}
}
if(this.backBufferBitmap != previousButmap)
{
if(previousButmap != null)
{
previousButmap.Dispose();
}
}
}
#endregion
#region 그래프 칠하기 - PaintGraph(graphics, bitmap, width, height)
/// <summary>
/// 그래프 칠하기
/// </summary>
/// <param name="graphics"></param>
/// <param name="bitmap">비트맵</param>
/// <param name="width">너비</param>
/// <param name="height">높이</param>
protected void PaintGraph(Graphics graphics, Bitmap bitmap, int width, int height)
{
List<Stack> stackList = new List<Stack>();
lock(this.stackListLockObject)
{
if(this.stackList.Count > 1)
{
stackList.AddRange(this.stackList);
this.stackList.Clear();
}
}
if(this.frontBufferBitmap == null)
{
graphics.Clear(Color.Black);
}
else
{
graphics.DrawImage(this.frontBufferBitmap, new Point(stackList.Count, 0));
if(stackList.Count < 1)
{
return;
}
}
for(int i = 0; i < stackList.Count; i++)
{
int colorArrayLength = stackList[0].ColorArray.Length;
for(int j = 0; j < height; j++)
{
bitmap.SetPixel(i, colorArrayLength - j - 1, stackList[stackList.Count - 1 - i].ColorArray[j]);
}
}
}
#endregion
#region 버퍼 교체하기 - SwapBuffer()
/// <summary>
/// 버퍼 교체하기
/// </summary>
protected void SwapBuffer()
{
Bitmap temporaryBitmap = this.frontBufferBitmap;
this.frontBufferBitmap = this.backBufferBitmap;
this.backBufferBitmap = temporaryBitmap;
}
#endregion
#region 다시 그리기 - Redraw()
/// <summary>
/// 다시 그리기
/// </summary>
protected void Redraw()
{
ResizeBuffer();
using(Graphics graphics = Graphics.FromImage(this.backBufferBitmap))
{
PaintGraph(graphics, this.backBufferBitmap, this.backBufferBitmap.Width, this.backBufferBitmap.Height);
}
SwapBuffer();
this.pictureBox.Image = this.frontBufferBitmap;
}
#endregion
#region 스택 리스트 추가하기 - AddStackList(sourceValueArray)
/// <summary>
/// 스택 리스트 추가하기
/// </summary>
/// <param name="sourceValueArray">소스 값 배열</param>
protected void AddStackList(float[] sourceValueArray)
{
List<Stack> stackList = new List<Stack>();
int k = 0;
float[] temporarValueArray = new float[SpectralWidth];
float[] fftArray = new float[temporarValueArray.Length];
int nextK = 0;
while(k <= sourceValueArray.Length - temporarValueArray.Length)
{
nextK = k + (temporarValueArray.Length >> 3);
for(int i = 0; i < temporarValueArray.Length; i++)
{
temporarValueArray[i] = sourceValueArray[k++];
}
FFT.ComputeForwardFFT(temporarValueArray, fftArray);
float[] targetValueArray = ShowFFT ? fftArray : temporarValueArray;
float minmiumValue = targetValueArray[0];
float maximumValue = targetValueArray[0];
for(int i = 0; i < targetValueArray.Length; i++)
{
minmiumValue = minmiumValue < targetValueArray[i] ? minmiumValue : targetValueArray[i];
maximumValue = maximumValue > targetValueArray[i] ? maximumValue : targetValueArray[i];
}
minmiumValue = float.IsNaN(minmiumValue) ? this.minimumValue : minmiumValue;
maximumValue = float.IsNaN(maximumValue) ? this.maximumValue : maximumValue;
minmiumValue = float.IsInfinity(minmiumValue) ? this.minimumValue : minmiumValue;
maximumValue = float.IsInfinity(maximumValue) ? this.maximumValue : maximumValue;
this.minimumValue = (255 * this.minimumValue + minmiumValue) / 256;
this.maximumValue = (255 * this.maximumValue + maximumValue) / 256;
stackList.Add(new Stack(targetValueArray, this.minimumValue, this.maximumValue));
k = nextK;
}
lock(this.stackListLockObject)
{
this.stackList.AddRange(stackList);
}
}
#endregion
#region 작업 스레드 처리하기 - ProcessWorkThread()
/// <summary>
/// 작업 스레드 처리하기
/// </summary>
protected void ProcessWorkThread()
{
float[] lastValueArray = null;
while(this.closed == false)
{
Thread.Sleep(100);
float[] valueArray = null;
lock(this.threadLockObject)
{
if(this.valueArrayList.Count > 0)
{
valueArray = this.valueArrayList[0];
this.valueArrayList.RemoveAt(0);
}
}
if(valueArray != null)
{
if(lastValueArray != null)
{
int spectralWidth = SpectralWidth;
float[] temporaryValueArray = new float[(int)(spectralWidth * 1.75f)];
int k = 0;
int swo = spectralWidth - (spectralWidth >> 3);
int lastValueArrayStart = lastValueArray.Length - swo;
lastValueArrayStart = lastValueArrayStart < 0 ? 0 : lastValueArrayStart;
for(int i = lastValueArrayStart; i < lastValueArray.Length; i++)
{
temporaryValueArray[k++] = lastValueArray[i];
}
for(int i = 0; i < swo && k < temporaryValueArray.Length && i < valueArray.Length; i++)
{
temporaryValueArray[k++] = valueArray[i];
}
AddStackList(temporaryValueArray);
}
AddStackList(valueArray);
lastValueArray = valueArray;
}
}
}
#endregion
}
}
▶ SoundSource.cs
using Microsoft.DirectX.DirectSound;
using Microsoft.Win32.SafeHandles;
using System;
using System.Collections.Generic;
using System.Threading;
namespace TestProject
{
/// <summary>
/// 사운드 소스
/// </summary>
public class SoundSource : IDisposable
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Delegate
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 데이터 처리하기 대리자 - ProcessDataDelegate(valueArray)
/// <summary>
/// 데이터 처리하기 대리자
/// </summary>
/// <param name="valueArray">값 배열</param>
public delegate void ProcessDataDelegate(float[] valueArray);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 데이터 처리시 이벤트 - ProcessData
/// <summary>
/// 데이터 처리시 이벤트
/// </summary>
public event ProcessDataDelegate ProcessData;
#endregion
#region FFT 데이터 처리시 이벤트 - ProcessFFTData
/// <summary>
/// FFT 데이터 처리시 이벤트
/// </summary>
public event ProcessDataDelegate ProcessFFTData;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 리소스 해제 잠금 객체
/// </summary>
protected object disposeLock = new object();
/// <summary>
/// 리소스 해제 여부
/// </summary>
protected bool disposed = false;
/// <summary>
/// 캡처
/// </summary>
protected Capture capture = null;
/// <summary>
/// 캡처 버퍼
/// </summary>
protected CaptureBuffer captureBuffer = null;
/// <summary>
/// 캡처 스레드
/// </summary>
protected Thread captureThread = null;
/// <summary>
/// 캡처 버퍼 길이
/// </summary>
protected int captureBufferLength = 0;
/// <summary>
/// 통지
/// </summary>
protected Notify notify;
/// <summary>
/// 버퍼 위치 이벤트
/// </summary>
protected AutoResetEvent bufferPositionEvent = null;
/// <summary>
/// 버퍼 위치 이벤트 핸들
/// </summary>
protected SafeWaitHandle bufferPositionEventHandle = null;
/// <summary>
/// 스레드 종료 이벤트
/// </summary>
protected ManualResetEvent threadTerminateEvent = null;
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 초 단위 버퍼 시간
/// </summary>
private const int BUFFER_TIME_IN_SECOND = 5;
/// <summary>
/// 초 단위 통지 포인트
/// </summary>
private const int NOTIFICATION_POINT_IN_SECOND = 2;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region GUID - GUID
/// <summary>
/// GUID
/// </summary>
public Guid GUID { get; set; }
#endregion
#region 설명 - Description
/// <summary>
/// 설명
/// </summary>
public string Description { get; set; }
#endregion
#region 디폴트 여부 - IsDefault
/// <summary>
/// 디폴트 여부
/// </summary>
public bool IsDefault
{
get
{
return GUID == Guid.Empty;
}
}
#endregion
#region 캡처 여부 - IsCapturing
/// <summary>
/// 캡처 여부
/// </summary>
public bool IsCapturing { get; protected set; }
#endregion
#region 샘플 비율 (단위 : HZ) - SampleRate
/// <summary>
/// 샘플 비율 (단위 : HZ)
/// </summary>
public int SampleRate { get; set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - SoundSource()
/// <summary>
/// 생성자
/// </summary>
public SoundSource()
{
CaptureDevicesCollection collection = new CaptureDevicesCollection();
for(int i = 0; i < collection.Count; i++)
{
if(collection[i].DriverGuid == Guid.Empty)
{
GUID = collection[i].DriverGuid;
Description = collection[i].Description;
break;
}
}
this.bufferPositionEvent = new AutoResetEvent(false);
this.bufferPositionEventHandle = this.bufferPositionEvent.SafeWaitHandle;
this.threadTerminateEvent = new ManualResetEvent(true);
}
#endregion
#region 생성자 - SoundSource(guid, description)
/// <summary>
/// 생성자
/// </summary>
/// <param name="guid">GUID</param>
/// <param name="description">설명</param>
public SoundSource(Guid guid, string description)
{
GUID = guid;
Description = description;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Destructor
#region 소멸자 - ~SoundSource()
/// <summary>
/// 소멸자
/// </summary>
~SoundSource()
{
Dispose(false);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 사운드 소스 리스트 구하기 - GetSoundSourceList()
/// <summary>
/// 사운드 소스 리스트 구하기
/// </summary>
/// <returns>사운드 소스 리스트</returns>
public static List<SoundSource> GetSoundSourceList()
{
CaptureDevicesCollection collection = new CaptureDevicesCollection();
List<SoundSource> sourceSourceList = new List<SoundSource>();
foreach(DeviceInformation deviceInformation in collection)
{
sourceSourceList.Add(new SoundSource(deviceInformation.DriverGuid, deviceInformation.Description));
}
return sourceSourceList;
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Instance
//////////////////////////////////////////////////////////////////////////////// Public
#region 시작하기 - Start()
/// <summary>
/// 시작하기
/// </summary>
public void Start()
{
if(IsCapturing)
{
return;
}
IsCapturing = true;
this.capture = new Capture(GUID);
WaveFormat waveFormat = new WaveFormat();
waveFormat.Channels = (short)this.capture.Caps.Channels;
waveFormat.BitsPerSample = 16;
waveFormat.SamplesPerSecond = 22000;
waveFormat.FormatTag = WaveFormatTag.Pcm;
waveFormat.BlockAlign = (short)((waveFormat.Channels * waveFormat.BitsPerSample + 7) / 8);
waveFormat.AverageBytesPerSecond = waveFormat.BlockAlign * waveFormat.SamplesPerSecond;
this.captureBufferLength = waveFormat.AverageBytesPerSecond * BUFFER_TIME_IN_SECOND;
CaptureBufferDescription captureBufferDescription = new CaptureBufferDescription();
captureBufferDescription.Format = waveFormat;
captureBufferDescription.BufferBytes = this.captureBufferLength;
this.captureBuffer = new CaptureBuffer(captureBufferDescription, this.capture);
int waitHandleCount = BUFFER_TIME_IN_SECOND * NOTIFICATION_POINT_IN_SECOND;
BufferPositionNotify[] bufferPositionNotifyArray = new BufferPositionNotify[waitHandleCount];
for(int i = 0; i < waitHandleCount; i++)
{
BufferPositionNotify bufferPositionNotify = new BufferPositionNotify();
bufferPositionNotify.Offset = (i + 1) * this.captureBufferLength / bufferPositionNotifyArray.Length - 1;
bufferPositionNotify.EventNotifyHandle = this.bufferPositionEventHandle.DangerousGetHandle();
bufferPositionNotifyArray[i] = bufferPositionNotify;
}
this.notify = new Notify(this.captureBuffer);
this.notify.SetNotificationPositions(bufferPositionNotifyArray);
this.threadTerminateEvent.Reset();
this.captureThread = new Thread(new ThreadStart(ProcessCaptureThread));
this.captureThread.Name = "Sound capture";
this.captureThread.Start();
}
#endregion
#region 중단하기 - Stop()
/// <summary>
/// 중단하기
/// </summary>
public void Stop()
{
if(IsCapturing)
{
IsCapturing = false;
this.threadTerminateEvent.Set();
this.captureThread.Join();
this.notify.Dispose();
this.captureBuffer.Dispose();
this.capture.Dispose();
}
}
#endregion
#region 리소스 해제하기 - Dispose(disposing)
/// <summary>
/// 리소스 해제하기
/// </summary>
/// <param name="disposing">리소스 해제 여부</param>
public void Dispose(bool disposing)
{
lock(this.disposeLock)
{
if(this.disposed)
{
return;
}
this.disposed = true;
}
GC.SuppressFinalize(this);
if(IsCapturing)
{
Stop();
}
if(this.bufferPositionEventHandle != null)
{
this.bufferPositionEventHandle.Dispose();
}
if(this.bufferPositionEvent != null)
{
this.bufferPositionEvent.Close();
}
if(this.threadTerminateEvent != null)
{
this.threadTerminateEvent.Close();
}
}
#endregion
#region 리소스 해제하기 - IDisposable.Dispose()
/// <summary>
/// 리소스 해제하기
/// </summary>
void IDisposable.Dispose()
{
Dispose(true);
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 값 처리하기 - ProcessValue(valueArray)
/// <summary>
/// 값 처리하기
/// </summary>
/// <param name="valueArray">값 배열</param>
protected void ProcessValue(short[] valueArray)
{
ProcessDataDelegate processData = ProcessData;
ProcessDataDelegate processFFTData = ProcessFFTData;
if((processData == null) && (processFFTData == null))
{
return;
}
int size = 2;
while(size < valueArray.Length)
{
size *= 2;
}
float[] xfArray = new float[size];
float[] xtArray = new float[valueArray.Length];
for(int i = 0; i < valueArray.Length; i++)
{
float value = (float)valueArray[i];
xfArray[i] = value;
xtArray[i] = value;
}
for(int i = valueArray.Length; i < size; i++)
{
xfArray[i] = 0;
}
if(processData != null)
{
processData(xtArray);
}
if(processFFTData != null)
{
float[] fftArray = new float[xfArray.Length];
FFT.ComputeForwardFFT(xfArray, fftArray);
processFFTData(fftArray);
}
}
#endregion
#region 캡처 스레드 처리하기 - ProcessCaptureThread()
/// <summary>
/// 캡처 스레드 처리하기
/// </summary>
private void ProcessCaptureThread()
{
this.captureBuffer.Start(true);
try
{
int nextCapturePosition = 0;
WaitHandle[] handleArray = new WaitHandle[] {this.threadTerminateEvent, this.bufferPositionEvent };
while(WaitHandle.WaitAny(handleArray) > 0)
{
int capturePosition;
int readPosition;
this.captureBuffer.GetCurrentPosition(out capturePosition, out readPosition);
int lockSize = readPosition - nextCapturePosition;
if(lockSize < 0)
{
lockSize += this.captureBufferLength;
}
if((lockSize & 1) != 0)
{
lockSize--;
}
int itemCount = lockSize >> 1;
short[] valueArray = (short[])this.captureBuffer.Read
(
nextCapturePosition,
typeof(short),
LockFlag.None,
itemCount
);
ProcessValue(valueArray);
nextCapturePosition = (nextCapturePosition + lockSize) % this.captureBufferLength;
}
}
catch(Exception exception)
{
Console.WriteLine(exception.Message);
}
finally
{
this.captureBuffer.Stop();
}
}
#endregion
}
}
▶ SoundPlayback.cs
using Microsoft.DirectX.DirectSound;
using System;
using System.IO;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 사운드 재생
/// </summary>
public class SoundPlayback : IDisposable
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 장치
/// </summary>
protected Device device = null;
/// <summary>
/// 2차 버퍼
/// </summary>
protected SecondaryBuffer secondaryBuffer = null;
/// <summary>
/// 위치
/// </summary>
protected int location = 0;
/// <summary>
/// 리소스 해제 여부
/// </summary>
protected bool disposed = false;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Destructor
#region 소멸자 - ~SoundPlayback()
/// <summary>
/// 소멸자
/// </summary>
~SoundPlayback()
{
Dispose(false);
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 셋업하기 - Setup(ownerControl, filePath)
/// <summary>
/// 셋업하기
/// </summary>
/// <param name="ownerControl">소유자 컨트롤</param>
/// <param name="filePath">파일 경로</param>
public void Setup(Control ownerControl, string filePath)
{
this.device = new Device();
this.device.SetCooperativeLevel(ownerControl, CooperativeLevel.Priority);
this.secondaryBuffer = new SecondaryBuffer(filePath, this.device);
this.secondaryBuffer.SetCurrentPosition(0);
}
#endregion
#region 데이터 구하기 - GetData(valueArray)
/// <summary>
/// 데이터 구하기
/// </summary>
/// <param name="valueArray">값 배열</param>
public bool GetData(float[] valueArray)
{
try
{
int bitCountPerSample = this.secondaryBuffer.Format.BitsPerSample;
byte[] byteArray = new byte[valueArray.Length * bitCountPerSample / 8];
MemoryStream stream = new MemoryStream(byteArray);
stream.Position = 0;
this.secondaryBuffer.Read(this.location, stream, valueArray.Length, LockFlag.None);
this.location += valueArray.Length;
int k = 0;
if(bitCountPerSample == 16)
{
for(int i = 0; i < valueArray.Length; i++)
{
int lower = byteArray[k++];
int upper = byteArray[k++];
valueArray[i] = (float)(lower + (upper * 256));
}
}
else
{
for(int i = 0; i < valueArray.Length; i++)
{
valueArray[i] = (float)(byteArray[k++]);
}
}
}
catch(Exception)
{
return false;
}
return true;
}
#endregion
#region 리소스 해제하기 - Dispose(disposing)
/// <summary>
/// 리소스 해제하기
/// </summary>
/// <param name="disposing">리소스 해제 여부</param>
public void Dispose(bool disposing)
{
if(this.disposed)
{
return;
}
this.disposed = true;
GC.SuppressFinalize(this);
this.secondaryBuffer.Dispose();
this.device.Dispose();
}
#endregion
#region 리소스 해제하기 - IDisposable.Dispose()
/// <summary>
/// 리소스 해제하기
/// </summary>
void IDisposable.Dispose()
{
Dispose(true);
}
#endregion
}
}
▶ MainForm.cs
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 메인 폼
/// </summary>
public partial class MainForm : Form
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region Field
/// <summary>
/// 사운드 소스
/// </summary>
protected SoundSource soundSource = null;
/// <summary>
/// 사운드 재생
/// </summary>
protected SoundPlayback soundPlayback = null;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainForm()
/// <summary>
/// 생성자
/// </summary>
public MainForm()
{
InitializeComponent();
this.listenButton.Click += listenButton_Click;
this.fileButton.Click += fileButton_Click;
this.timer.Tick += timer_Tick;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 종료시 처리하기 - OnClosing(e)
/// <summary>
/// 종료시 처리하기
/// </summary>
/// <param name="e">이벤트 인자</param>
protected override void OnClosing(CancelEventArgs e)
{
if(this.soundSource != null)
{
this.soundSource.Dispose(true);
}
if(this.soundPlayback != null)
{
this.soundPlayback.Dispose(true);
}
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 사운드 소스 데이터 처리하기 - soundSource_ProcessData(valueArray)
/// <summary>
/// 사운드 소스 데이터 처리하기
/// </summary>
/// <param name="valueArray">값 배열</param>
private void soundSource_ProcessData(float[] valueArray)
{
this.soundVisualizer.AppendData(valueArray);
this.soundVisualizer.StartTimer();
}
#endregion
#region 사운드 소스 FFT 데이터 처리하기 - soundSource_ProcessFFTData(valueArray)
/// <summary>
/// 사운드 소스 FFT 데이터 처리하기
/// </summary>
/// <param name="valueArray">값 배열</param>
private void soundSource_ProcessFFTData(float[] valueArray)
{
float[] temporaryValueArray = new float[valueArray.Length / 2];
for(int i = 0; i < temporaryValueArray.Length; i++)
{
temporaryValueArray[i] = valueArray[i];
}
if(this.graphControl.LineList.Count < 1)
{
this.graphControl.LineList.Add(new Line());
}
this.graphControl.LineList[0].Update(temporaryValueArray);
this.graphControl.StartTimer();
}
#endregion
#region 실시간 청취 버튼 클릭시 처리하기 - listenButton_Click(sender, e)
/// <summary>
/// 실시간 청취 버튼 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void listenButton_Click(object sender, EventArgs e)
{
if(this.soundSource != null)
{
if(this.soundSource.IsCapturing)
{
this.soundSource.Stop();
}
else
{
this.soundSource.Start();
}
}
else
{
this.soundSource = new SoundSource();
this.soundSource.ProcessData += soundSource_ProcessData;
this.soundSource.ProcessFFTData += soundSource_ProcessFFTData;
this.soundSource.Start();
}
}
#endregion
#region 파일 분석 버튼 클릭시 처리하기 - fileButton_Click(sender, e)
/// <summary>
/// 파일 분석 버튼 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void fileButton_Click(object sender, EventArgs e)
{
if(this.soundPlayback == null)
{
this.soundPlayback = new SoundPlayback();
}
DialogResult result = this.openFileDialog.ShowDialog();
if(result != DialogResult.OK)
{
return;
}
this.soundPlayback.Setup(this, this.openFileDialog.FileName);
this.timer.Enabled = true;
}
#endregion
#region 타이머 틱 처리하기 - timer_Tick(sender, e)
/// <summary>
/// 타이머 틱 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void timer_Tick(object sender, EventArgs e)
{
float[] valueArray = new float[1024];
if(this.soundPlayback.GetData(valueArray) == false)
{
this.timer.Enabled = false;
return;
}
soundSource_ProcessData(valueArray);
float[] fftArray = new float[valueArray.Length];
FFT.ComputeForwardFFT(valueArray, fftArray);
soundSource_ProcessFFTData(fftArray);
soundSource_ProcessData(fftArray);
}
#endregion
}
}
※ 32비트 버전으로 실행해야 한다.
728x90
반응형
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM/.NET5] WaveInEvent 클래스 : WAV 파일 레코딩하기 (0) | 2021.12.28 |
---|---|
[C#/WINFORM/.NET5] WaveOutEvent 클래스 : Volume 속성을 사용해 볼륨 설정하기 (0) | 2021.12.26 |
[C#/WINFORM/.NET5] WaveOutEvent 클래스 : 오디오 파일 재생하기 (기능 개선) (0) | 2021.12.26 |
[C#/WINFORM/.NET5] WaveOutEvent 클래스 : 오디오 파일 재생하기 (0) | 2021.12.26 |
[C#/WINFORM/.NET5] 마이크 입력 스펙트로그램 표시하기 (0) | 2021.12.18 |
[C#/WINFORM] NativeWindow 클래스 : 마우스 캡처 변경시 이벤트 전달하기 (0) | 2021.12.06 |
[C#/WINFORM] 마우스 히트 테스트(Hit Test) 사용하기 (0) | 2021.12.06 |
[C#/WINFORM] 특정 영역에서 마우스 커서 설정하기 (0) | 2021.12.06 |
[C#/WINFORM] 마우스 드래그 대상 이미지 표시하기 (0) | 2021.12.06 |
[C#/WINFORM] 카카오 링크를 사용해 나에게 카카오톡 메시지 보내기 (기능 개선) (0) | 2021.11.24 |
댓글을 달아 주세요