227 lines
7.6 KiB
C#
227 lines
7.6 KiB
C#
|
using LibDynamics;
|
|||
|
using FftComplex = FftSharp.Complex;
|
|||
|
using FftTransform = FftSharp.Transform;
|
|||
|
|
|||
|
namespace LibAudioVisualizer
|
|||
|
{
|
|||
|
public class Visualizer
|
|||
|
{
|
|||
|
//private int _m;
|
|||
|
private double[] _sampleData;
|
|||
|
private DateTime _lastTime;
|
|||
|
private SecondOrderDynamicsForArray _dynamics;
|
|||
|
private int _size;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 采样数据
|
|||
|
/// </summary>
|
|||
|
public double[] SampleData => _sampleData;
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 尺寸
|
|||
|
/// </summary>
|
|||
|
public int Size
|
|||
|
{
|
|||
|
get => _size; set
|
|||
|
{
|
|||
|
if (!(Get2Flag(value)))
|
|||
|
throw new ArgumentException("长度必须是 2 的 n 次幂");
|
|||
|
|
|||
|
_size = value;
|
|||
|
_sampleData = new double[value];
|
|||
|
_dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, value / 2);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public int OutputSize => Size / 2;
|
|||
|
|
|||
|
public Visualizer(int size)
|
|||
|
{
|
|||
|
if (!(Get2Flag(size)))
|
|||
|
throw new ArgumentException("大小必须是 2 的 n 次幂", nameof(size));
|
|||
|
|
|||
|
_lastTime = DateTime.Now;
|
|||
|
_sampleData = new double[size];
|
|||
|
_dynamics = new SecondOrderDynamicsForArray(1, 1, 1, 0, size / 2);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 判断是否是 2 的整数次幂
|
|||
|
/// </summary>
|
|||
|
/// <param name="num"></param>
|
|||
|
/// <returns></returns>
|
|||
|
private bool Get2Flag(int num)
|
|||
|
{
|
|||
|
if (num < 1)
|
|||
|
return false;
|
|||
|
return (num & num - 1) == 0;
|
|||
|
}
|
|||
|
|
|||
|
public void PushSampleData(double[] waveData)
|
|||
|
{
|
|||
|
if (waveData.Length > _sampleData.Length)
|
|||
|
{
|
|||
|
Array.Copy(waveData, waveData.Length - _sampleData.Length, _sampleData, 0, _sampleData.Length);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Array.Copy(_sampleData, waveData.Length, _sampleData, 0, _sampleData.Length - waveData.Length);
|
|||
|
Array.Copy(waveData, 0, _sampleData, _sampleData.Length - waveData.Length, waveData.Length);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void PushSampleData(double[] waveData, int count)
|
|||
|
{
|
|||
|
if (count > _sampleData.Length)
|
|||
|
{
|
|||
|
Array.Copy(waveData, count - _sampleData.Length, _sampleData, 0, _sampleData.Length);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Array.Copy(_sampleData, count, _sampleData, 0, _sampleData.Length - count);
|
|||
|
Array.Copy(waveData, 0, _sampleData, _sampleData.Length - count, count);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 获取频谱数据 (数据已经删去共轭部分)
|
|||
|
/// </summary>
|
|||
|
/// <returns></returns>
|
|||
|
public double[] GetSpectrumData()
|
|||
|
{
|
|||
|
DateTime now = DateTime.Now;
|
|||
|
double deltaTime = (now - _lastTime).TotalSeconds;
|
|||
|
_lastTime = now;
|
|||
|
|
|||
|
int len = _sampleData.Length;
|
|||
|
FftComplex[] data = new FftComplex[len];
|
|||
|
|
|||
|
for (int i = 0; i < len; i++)
|
|||
|
data[i] = new FftComplex(_sampleData[i], 0);
|
|||
|
|
|||
|
FftTransform.FFT(data);
|
|||
|
|
|||
|
int halfLen = len / 2;
|
|||
|
double[] spectrum = new double[halfLen]; // 傅里叶变换结果左右对称, 只需要取一半
|
|||
|
for (int i = 0; i < halfLen; i++)
|
|||
|
spectrum[i] = data[i].Magnitude / len;
|
|||
|
|
|||
|
var window = new FftSharp.Windows.Bartlett();
|
|||
|
window.Create(halfLen);
|
|||
|
window.ApplyInPlace(spectrum, false);
|
|||
|
|
|||
|
//return spectrum;
|
|||
|
return _dynamics.Update(deltaTime, spectrum);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 取指定频率内的频谱数据
|
|||
|
/// </summary>
|
|||
|
/// <param name="spectrum">源频谱数据</param>
|
|||
|
/// <param name="sampleRate">采样率</param>
|
|||
|
/// <param name="frequency">目标频率</param>
|
|||
|
/// <returns></returns>
|
|||
|
public static double[] TakeSpectrumOfFrequency(double[] spectrum, double sampleRate, double frequency)
|
|||
|
{
|
|||
|
double frequencyPerSampe = sampleRate / spectrum.Length;
|
|||
|
|
|||
|
int lengthInNeed = (int)(Math.Min(frequency / frequencyPerSampe, spectrum.Length));
|
|||
|
double[] result = new double[lengthInNeed];
|
|||
|
Array.Copy(spectrum, 0, result, 0, lengthInNeed);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// 简单的数据模糊
|
|||
|
/// </summary>
|
|||
|
/// <param name="data">数据</param>
|
|||
|
/// <param name="radius">模糊半径</param>
|
|||
|
/// <returns>结果</returns>
|
|||
|
public static double[] GetBlurry(double[] data, int radius)
|
|||
|
{
|
|||
|
double[] GetWeights(int radius)
|
|||
|
{
|
|||
|
double Gaussian(double x) => Math.Pow(Math.E, (-4 * x * x)); // 憨批高斯函数
|
|||
|
|
|||
|
int len = 1 + radius * 2; // 长度
|
|||
|
int end = len - 1; // 最后的索引
|
|||
|
double radiusF = (double)radius; // 半径浮点数
|
|||
|
double[] weights = new double[len]; // 权重
|
|||
|
|
|||
|
for (int i = 0; i <= radius; i++) // 先把右边的权重算出来
|
|||
|
weights[radius + i] = Gaussian(i / radiusF);
|
|||
|
for (int i = 0; i < radius; i++) // 把右边的权重拷贝到左边
|
|||
|
weights[i] = weights[end - i];
|
|||
|
|
|||
|
double total = weights.Sum();
|
|||
|
for (int i = 0; i < len; i++) // 使权重合为 0
|
|||
|
weights[i] = weights[i] / total;
|
|||
|
|
|||
|
return weights;
|
|||
|
}
|
|||
|
|
|||
|
void ApplyWeights(double[] buffer, double[] weights)
|
|||
|
{
|
|||
|
int len = buffer.Length;
|
|||
|
for (int i = 0; i < len; i++)
|
|||
|
buffer[i] = buffer[i] * weights[i];
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
double[] weights = GetWeights(radius);
|
|||
|
double[] buffer = new double[1 + radius * 2];
|
|||
|
|
|||
|
double[] result = new double[data.Length];
|
|||
|
if (data.Length < radius)
|
|||
|
{
|
|||
|
Array.Fill(result, data.Average());
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
for (int i = 0; i < radius; i++)
|
|||
|
{
|
|||
|
Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省
|
|||
|
for (int j = 0; j < radius; j++) //
|
|||
|
{
|
|||
|
buffer[radius + 1 + j] = data[i + j];
|
|||
|
}
|
|||
|
|
|||
|
ApplyWeights(buffer, weights);
|
|||
|
result[i] = buffer.Sum();
|
|||
|
}
|
|||
|
|
|||
|
for (int i = radius; i < data.Length - radius; i++)
|
|||
|
{
|
|||
|
for (int j = 0; j < radius; j++) //
|
|||
|
{
|
|||
|
buffer[j] = data[i - j];
|
|||
|
}
|
|||
|
|
|||
|
buffer[radius] = data[i];
|
|||
|
|
|||
|
for (int j = 0; j < radius; j++) //
|
|||
|
{
|
|||
|
buffer[radius + j + 1] = data[i + j];
|
|||
|
}
|
|||
|
|
|||
|
ApplyWeights(buffer, weights);
|
|||
|
result[i] = buffer.Sum();
|
|||
|
}
|
|||
|
|
|||
|
for (int i = data.Length - radius; i < data.Length; i++)
|
|||
|
{
|
|||
|
Array.Fill(buffer, data[i], 0, radius + 1); // 填充缺省
|
|||
|
for (int j = 0; j < radius; j++) //
|
|||
|
{
|
|||
|
buffer[radius + 1 + j] = data[i - j];
|
|||
|
}
|
|||
|
|
|||
|
ApplyWeights(buffer, weights);
|
|||
|
result[i] = buffer.Sum();
|
|||
|
}
|
|||
|
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|