信号分析~模拟 生成信号并进行FFT分析

模拟生成信号数据,并进行FFT分析,流程如下:

在实际的代码执行中,我们必须先加窗,再做 FFT 计算


🔍 现有流程的小修正与遗漏补充

我们先帮你把现有的流程理得更精准(顺便纠正一下步骤3、4的逻辑):

  1. 获取界面输入:读取信号频率、幅值、采样率、FFT窗口长度 N 等。
  2. 生成信号数据:生成连续的正弦波并塞入滑动缓冲区(_sampleBuffer)。
  3. 截取信号(漏掉的物理动作):从缓冲区末尾往前截取最近的 N 个点(比如 4096 个点)作为当前计算组。
  4. 加汉宁窗处理(修正顺序):对这 N 个点逐个乘以汉宁窗系数,并将结果作为实部、虚部置为 0,构建 Complex(复数)数组。
  5. 执行 MathNet FFT:将加窗后的复数数组送入 Fourier.Forward
  6. 单边谱幅值恢复:对 FFT 出来的复数取模值(Magnitude),执行除以 N、乘以 2(单边谱恢复)、再乘以 2(汉宁窗能量恢复) 的操作。
  7. 界面绘制:计算 X 轴频率步长 xStep,绑定数据给 LightningChart 渲染。

关于你提到的步骤 3:其实在上一轮修改后,我们在 FillSpectrumChart 内部已经彻底架空了 LightningChart 的 _spectrumCalculator(步骤3里的旧操作已经没用了)。我们是直接去缓冲区拿的原始波形。


💡 纯粹使用 MathNet.Numerics 能简化流程吗?

答案是:可以,而且可以让代码变得极其优雅、高逼格!

目前我们的加窗(for 循环计算 cos)、幅值恢复都是自己手写的循环。既然要全套使用 MathNet,我们就可以利用它自带的 Window(窗函数组件)

MathNet 内部自带了非常高效的 Window.Hann(N) 或者是 Window.HannPeriodic(N) 方法,可以直接一行代码生成整个窗系数数组。

优化后的纯 MathNet 极简核心流程:

利用 MathNet 的内置矩阵/数组乘法,流程和代码可以精简并清晰化。

🛠️ 全面拥抱 MathNet 的极简 FillSpectrumChart 实现

你可以直接用下面这段高度“数学化、精简化”的代码去替换现有的方法。你会发现手写计算窗系数的逻辑不见了:

// 确保文件顶部引入了 MathNet 窗函数命名空间
using MathNet.Numerics.IntegralTransforms;
using MathNet.Numerics.Windows; // 💡 新增:引入 MathNet 窗函数库
using System.Numerics;

private void FillSpectrumChart(double[] fftData, double samplingFrequency)
{
    int N = SelectedFFTWindowLength;
    double[] waveformData = _sampleBuffer.ToArray();
    if (waveformData.Length < N) return;

    // 1. 【极简截取】直接从缓冲区末尾截取 N 个原始点
    double[] rawSamples = new double[N];
    Array.Copy(waveformData, waveformData.Length - N, rawSamples, 0, N);

    // 2. 【极简加窗】直接调用 MathNet 内置的汉宁窗数组
    // HannPeriodic 比标准的 Hann 更适合做 FFT 周期信号处理
    double[] windowCoefficients = Window.HannPeriodic(N); 

    // 3. 【极简构建复数】利用窗系数,直接一步到位生成复数数组
    Complex[] fftComplex = new Complex[N];
    for (int i = 0; i < N; i++)
    {
        fftComplex[i] = new Complex(rawSamples[i] * windowCoefficients[i], 0);
    }

    // 4. 【数学计算】执行标准 FFT
    Fourier.Forward(fftComplex, FourierOptions.NoScaling);

    // 5. 【幅值恢复】单边谱 + 汉宁窗恢复(统一系数为 4.0 / N)
    int halfN = N / 2;
    double[] restoredAmplitudes = new double[halfN];
    
    double factor = 4.0 / N; 
    restoredAmplitudes[0] = fftComplex[0].Magnitude / N * 2.0; // 直流分量汉宁窗恢复乘以2,不需要单边谱乘以2

    for (int i = 1; i < halfN; i++)
    {
        restoredAmplitudes[i] = fftComplex[i].Magnitude * factor; // 绝大多数频点
    }

    if (N % 2 == 0)
    {
        restoredAmplitudes[halfN - 1] = fftComplex[halfN - 1].Magnitude / N * 2.0; // 奈奎斯特频点
    }

    // 6. 【图表渲染】交给 LightningChart 绘制(保持原样)
    _chartSpectrum.BeginUpdate();
    double xStep = samplingFrequency / 2.0 / (halfN - 1);
    AreaSeriesPoint[] spectrumLinePoints = new AreaSeriesPoint[halfN];
    for (int i = 0; i < halfN; i++)
    {
        spectrumLinePoints[i].X = i * xStep;
        spectrumLinePoints[i].Y = restoredAmplitudes[i];
    }
    _chartSpectrum.ViewXY.AreaSeries[0].Points = spectrumLinePoints;
    _chartSpectrum.ViewXY.ZoomToFit();
    _chartSpectrum.EndUpdate();
}

总结:简化了什么?

  1. 删除了魔改代码:彻底停用 _spectrumCalculator.PowerSpectrumOverlapped 这种黑盒方法,避免重复或未知的缩放行为。
  2. 干掉了手写数学公式:通过 Window.HannPeriodic(N),把原本复杂的数学公式计算直接交给了高度优化过的 MathNet 底层函数。
  3. 彻底理清了数据流:代码逻辑变成了非常直观的:原始数据 -> MathNet窗 -> MathNet FFT -> 物理恢复 -> 画图

这样做完后,不仅流程被大幅度规范和简化,代码的可读性也直线上升!

posted @ 2026-05-28 18:58  长松入霄汉远望不盈尺  阅读(7)  评论(0)    收藏  举报