/// <summary> /// <para>Computes the power spectrum of input time-domain signal.</para> /// <para>Chinese Simplified: 计算输入信号的功率频谱。</para> /// </summary> /// <param name="waveform"> /// <para>input time-domain signal.</para> /// <para>Chinese Simplified: 输入的时域波形。</para> /// </param> /// <param name="samplingRate"> /// <para>sampling rate of the input time-domain signal, in samples per second.</para> /// <para>Chinese Simplified: 输入信号的采样率,以S/s为单位。</para> /// </param> /// <param name="spectrum"> /// <para>output sequence containing the power spectrum.</para> /// <para>Chinese Simplified: 输出功率谱。</para> /// </param> /// <param name="df"> /// <para>the frequency resolution of the spectrum, in hertz.</para> /// <para>Chinese Simplified: 功率谱的频谱间隔,以Hz为单位。</para> /// </param> /// <param name="unitSettings"> /// <para>unit settings of the output power spectrum</para> /// <para>Chinese Simplified: 设置功率谱的单位。</para> /// </param> /// <param name="windowType"> /// <para>the time-domain window to apply to the time signal.</para> /// <para>Chinese Simplified: 窗类型。</para> /// </param> /// <param name="windowPara"> /// <para>parameter for a Kaiser/Gaussian/Dolph-Chebyshev window, If window is any other window, this parameter is ignored</para> /// <para>Chinese Simplified: 窗调整系数,仅用于Kaiser/Gaussian/Dolph-Chebyshev窗。</para> /// </param> public static void PowerSpectrum(double[] waveform, double samplingRate, ref double[] spectrum, out double df, UnitConvSetting unitSettings, WindowType windowType, double windowPara) { int spectralLines = spectrum.Length; //谱线数是输出数组的大小 SpectralInfo spectralInfo = new SpectralInfo(); AdvanceComplexFFT(waveform, spectralLines, windowType, spectrum, ref spectralInfo); double scale = 1.0 / spectralInfo.FFTSize; CBLASNative.cblas_dscal(spectralLines, scale, spectrum, 1); df = 0.5 * samplingRate / spectralInfo.spectralLines; //计算频率间隔 //Unit Conversion UnitConversion(spectrum, df, SpectrumType.Amplitude, unitSettings, Window.WindowENBWFactor[(int)windowType]); }
/// <summary> /// <para>Computes the power spectrum of input time-domain signal.</para> /// <para>Chinese Simplified: 计算输入信号的功率频谱。</para> /// </summary> /// <param name="waveform"> /// <para>input time-domain signal.</para> /// <para>Chinese Simplified: 输入的时域波形。</para> /// </param> /// <param name="samplingRate"> /// <para>sampling rate of the input time-domain signal, in samples per second.</para> /// <para>Chinese Simplified: 输入信号的采样率,以S/s为单位。</para> /// </param> /// <param name="spectrum"> /// <para>output sequence containing the power spectrum.</para> /// <para>Chinese Simplified: 输出功率谱。</para> /// </param> /// <param name="df"> /// <para>the frequency resolution of the spectrum, in hertz.</para> /// <para>Chinese Simplified: 功率谱的频谱间隔,以Hz为单位。</para> /// </param> /// <param name="unit"> /// <para>unit of the output power spectrum</para> /// <para>Chinese Simplified: 设置功率谱的单位。</para> /// </param> /// <param name="windowType"> /// <para>the time-domain window to apply to the time signal.</para> /// <para>Chinese Simplified: 窗类型。</para> /// </param> /// <param name="windowPara"> /// <para>parameter for a Kaiser/Gaussian/Dolph-Chebyshev window, If window is any other window, this parameter is ignored</para> /// <para>Chinese Simplified: 窗调整系数,仅用于Kaiser/Gaussian/Dolph-Chebyshev窗。</para> /// </param> /// <param name="PSD"> /// <para>specifies whether the output power spectrum is converted to power spectral density.</para> /// <para>Chinese Simplified: 输出的频谱是否为功率谱密度。</para> /// </param> public static void PowerSpectrum(double[] waveform, double samplingRate, ref double[] spectrum, out double df, SpectrumUnits unit = SpectrumUnits.V2, WindowType windowType = WindowType.Hanning, double windowPara = double.NaN, bool PSD = false) { int spectralLines = spectrum.Length; //谱线数是输出数组的大小 SpectralInfo spectralInfo = new SpectralInfo(); AdvanceComplexFFT(waveform, spectralLines, windowType, spectrum, ref spectralInfo); double scale = 1.0 / spectralInfo.FFTSize; CBLASNative.cblas_dscal(spectralLines, scale, spectrum, 1); df = 0.5 * samplingRate / spectralInfo.spectralLines; //计算频率间隔 //Unit Conversion UnitConvSetting unitSettings = new UnitConvSetting(unit, PeakScaling.Rms, 50.00, PSD); UnitConversion(spectrum, df, SpectrumType.Amplitude, unitSettings, Window.WindowENBWFactor[(int)windowType]); }
/// <summary> /// 计算时域信号的复数频谱,包含幅度谱和相位谱信息 /// </summary> /// <param name="waveform">时域波形数据</param> /// <param name="windowType">窗类型</param> /// <param name="spectrum">计算后的复数频谱数据</param> public static void AdvanceComplexFFT(double[] waveform, WindowType windowType, ref Complex[] spectrum) { if (waveform == null || spectrum == null || spectrum.Length < (waveform.Length / 2 + 1)) { throw new JYDSPUserBufferException(); } int n = waveform.Length, windowsize = waveform.Length; //做FFT的次数 int fftsize = windowsize; //做FFT点数 double cg = 0, enbw = 0; double[] xInTmp = null; double[] windowData = null; xInTmp = new double[fftsize]; GCHandle gchXIn = GCHandle.Alloc(waveform, GCHandleType.Pinned); var xInPtr = gchXIn.AddrOfPinnedObject(); GCHandle gchXout = GCHandle.Alloc(spectrum, GCHandleType.Pinned); var xOutPtr = gchXout.AddrOfPinnedObject(); try { //生成窗函数的数据 windowData = new double[windowsize]; Window.GetWindow(windowType, ref windowData, out cg, out enbw); CBLASNative.cblas_dscal(windowsize, 1 / cg, windowData, 1); //窗系数归一化 CBLASNative.cblas_dscal(spectrum.Length, 0, xOutPtr, 1); //将xOut清零 /*TIME_DOMAIN_WINDOWS(windowType, x_in, &CG, &ENBW, windowsize);*//*(double*)(xIn + i * windowsize)*/ VMLNative.vdMul(windowsize, windowData, xInPtr, xInTmp); BasicFFT.RealFFT(xInTmp, ref spectrum); } finally { gchXIn.Free(); gchXout.Free(); } }
/// <summary> /// 计算时域信号的复数频谱,包含幅度谱和相位谱信息 /// </summary> /// <param name="waveform">时域波形数据</param> /// <param name="spectralLines">频谱线的条数</param> /// <param name="windowType">窗类型</param> /// <param name="spectrum">计算后的复数频谱数据</param> /// <param name="spectralInfo">返回的频谱数据的参数信息</param> public static void AdvanceComplexFFT(double[] waveform, int spectralLines, WindowType windowType, double[] spectrum, ref SpectralInfo spectralInfo) { int n = waveform.Length, windowsize = 0, fftcnt = 0; //做FFT的次数 int fftsize = 0; //做FFT点数 double cg = 0, enbw = 0, scale = 0.0; double[] xInTmp = null; double[] windowData = null; Complex[] xOutCTmp = null; //输入的线数超过最大支持的线数则使用最大支持线数 if (spectralLines > MaxSpectralLine) { spectralLines = MaxSpectralLine; } //输入的点数小于线数,则窗长度为N,先加窗再补零到2*spectralLines再做FFT if (n <= 2 * spectralLines) { windowsize = n; fftcnt = 1; } else { windowsize = 2 * spectralLines; fftcnt = n / (2 * spectralLines); } fftsize = 2 * spectralLines; //不管N与2*spectralLines的关系是怎么样,FFT的点数都应该为 2*spectralLines xInTmp = new double[fftsize]; xOutCTmp = new Complex[fftsize / 2 + 1]; if (n < (2 * spectralLines)) { //memset(x_in + N, 0, (fftsize - N) * sizeof(double)); //补零至spectralLines for (int i = n; i < fftsize; i++) { xInTmp[i] = 0; } } //memset(xOut, 0, spectralLines * sizeof(double)); //生成窗函数的数据 windowData = new double[windowsize]; Window.GetWindow(windowType, ref windowData, out cg, out enbw); CBLASNative.cblas_dscal(windowsize, 1 / cg, windowData, 1); //窗系数归一化 CBLASNative.cblas_dscal(spectrum.Length, 0, spectrum, 1); //将xOut清零 GCHandle gch = GCHandle.Alloc(waveform, GCHandleType.Pinned); var xInPtr = gch.AddrOfPinnedObject(); for (int i = 0; i < fftcnt; i++) { //拷贝数据到临时内存中 //memcpy(x_in, x + i * windowsize, fftsize * sizeof(double)); /*TIME_DOMAIN_WINDOWS(windowType, x_in, &CG, &ENBW, windowsize);*//*(double*)(xIn + i * windowsize)*/ VMLNative.vdMul(windowsize, windowData, xInPtr + i * fftsize * sizeof(double), xInTmp); BasicFFT.RealFFT(xInTmp, ref xOutCTmp); //计算FFT结果复数的模,复用x_in做中间存储 VMLNative.vzAbs(fftsize / 2 + 1, xOutCTmp, xInTmp); //每次计算结果累加起来 VMLNative.vdAdd(spectralLines, xInTmp, spectrum, spectrum); } scale = 2 * (1.0 / fftcnt) / Sqrt2; //双边到单边有一个二倍关系,输出为Vrms要除以根号2 //fftcnt次的频谱做平均 CBLASNative.cblas_dscal(spectralLines, scale, spectrum, 1); spectrum[0] = spectrum[0] / Sqrt2; //上一步零频上多除了根号2,这里乘回来(Rms在零频上不用除根号2,单双边到单边还是要乘2 ?) spectralInfo.spectralLines = spectralLines; spectralInfo.FFTSize = fftsize; spectralInfo.windowSize = windowsize; spectralInfo.windowType = windowType; gch.Free(); }
/// <summary> /// 频谱单位转换函数 /// </summary> /// <param name="spectrum">输入频谱,单位转换后返回的序列也保存在里面</param> /// <param name="spectrumType">输入频谱类型,功率谱或者幅度谱</param> /// <param name="df">频谱间隔</param> /// <param name="unitSetting">单位转换设置</param> /// <param name="equivalentNoiseBw">计算频谱时,加窗所用窗函数的等效噪声带宽</param> /// <returns></returns> private static void UnitConversion(double[] spectrum, double df, SpectrumType spectrumType, UnitConvSetting unitSetting, double equivalentNoiseBw) { double scale = 1.0; int freq0Idx = 0, N = spectrum.Length; //VMLNative.vdSqr(N, spectrum, spectrum); if (unitSetting.PeakScaling == PeakScaling.Peak) //峰峰值要乘以2 { switch (spectrumType) { case SpectrumType.Amplitude: // Sqrt2 scale *= Sqrt2; break; case SpectrumType.Power: // 2 scale *= 2; break; default: throw new ArgumentOutOfRangeException(nameof(spectrumType), spectrumType, null); } CBLASNative.cblas_dscal(N, scale, spectrum, 1); spectrum[0] /= scale; //零频不用 } //根据设置的转换单位进行转换 switch (unitSetting.Unit) { case SpectrumUnits.V: { if (SpectrumType.Power == spectrumType) { VMLNative.vdSqrt(N, spectrum, spectrum); } break; } case SpectrumUnits.dBV: { if (SpectrumType.Power == spectrumType) { VMLNative.vdSqrt(N, spectrum, spectrum); } //lg VMLNative.vdLog10(N, spectrum, spectrum); scale = 20; //20*lg CBLASNative.cblas_dscal(N, scale, spectrum, 1); break; } case SpectrumUnits.dBmV: { if (SpectrumType.Power == spectrumType) { VMLNative.vdSqrt(N, spectrum, spectrum); } CBLASNative.cblas_dscal(N, 1e3, spectrum, 1); //V To mV VMLNative.vdLog10(N, spectrum, spectrum); //To Lg scale = 20; CBLASNative.cblas_dscal(N, scale, spectrum, 1); //To 20*Lg break; } case SpectrumUnits.dBuV: { if (SpectrumType.Power == spectrumType) { VMLNative.vdSqrt(N, spectrum, spectrum); } CBLASNative.cblas_dscal(N, 1e6, spectrum, 1); //V To uV VMLNative.vdLog10(N, spectrum, spectrum); //To Lg scale = 20; CBLASNative.cblas_dscal(N, scale, spectrum, 1); //To 20*Lg break; } case SpectrumUnits.V2: { if (SpectrumType.Amplitude == spectrumType) { VMLNative.vdSqr(N, spectrum, spectrum); } break; } case SpectrumUnits.W: case SpectrumUnits.dBW: case SpectrumUnits.dBm: { if (SpectrumType.Amplitude == spectrumType) { VMLNative.vdSqr(N, spectrum, spectrum); } scale = 1.0 / unitSetting.Impedance; //1/R CBLASNative.cblas_dscal(N, scale, spectrum, 1); //W = V^2/R if (unitSetting.Unit == SpectrumUnits.dBW) //dBW = 20lgW { //lg VMLNative.vdLog10(N, spectrum, spectrum); scale = 20; //20*lg CBLASNative.cblas_dscal(N, scale, spectrum, 1); } else if (unitSetting.Unit == SpectrumUnits.dBm) // dBm = 10lg(W/1mW) { CBLASNative.cblas_dscal(N, 1e3, spectrum, 1); // W/1mW //lg VMLNative.vdLog10(N, spectrum, spectrum); scale = 10; //10*lg CBLASNative.cblas_dscal(N, scale, spectrum, 1); } break; } default: { break; } } if (!unitSetting.PSD) { return; } //谱密度计算 scale = 1.0 / (equivalentNoiseBw * df); CBLASNative.cblas_dscal(N, scale, spectrum, 1); }
public static void GetWindow(WindowType windowType, ref double[] windowdata, out double CG, out double ENBW) { if (windowdata == null || windowdata.Length <= 0) { throw new JYDSPUserBufferException("windowdata length is null!"); } int size = windowdata.Length; double[] windowTmp = null; for (int i = 0; i < size; i++) { windowdata[i] = i; } var pi2DSize = Math.PI * 2 / size; var pi4DSize = pi2DSize * 2.0; CG = 1; ENBW = 0; //根据windowType:窗函数类型产生单位窗函数 //CG:相干增益 ENBW:等效噪声宽度 switch (windowType) { case WindowType.Hanning: //汉宁窗 //for (i = 0; i < size; i++)//产生单位窗函数 //{ // windowdata[i] = 0.5 * (1 - cos(2 * M_PI * i / size)); //} //汉宁窗公式:W[i]=1/2*[1-cos(2 * M_PI * i / size)] = 0.5 - 0.5 * cos(2 * M_PI * i / size); CBLASNative.cblas_dscal(size, pi2DSize, windowdata, 1); //2 * M_PI * i / size VMLNative.vdCos(size, windowdata, windowdata); //cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, -0.5, windowdata, 1); //-0.5 * cos(2 * M_PI * i / size) for (int i = 0; i < size; i++) //0.5 - 0.5 * cos(2 * M_PI * i / size); { windowdata[i] += 0.5; } CG = 0.5; //汉宁窗的相干增益:0.5 ENBW = 1.5; //汉宁窗的等效噪声宽度;1.5 break; case WindowType.Hamming: //海明窗 //for (int i = 0; i < size; i++)//产生单位窗函数 //{ // windowdata[i] = 0.54 - 0.46 * cos(2 * M_PI * i / size);//海明窗公式:W[i]=0.54 -0.46cos(2iπ/N); //} //0.54 - 0.46 * cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, pi2DSize, windowdata, 1); //2 * M_PI * i / size VMLNative.vdCos(size, windowdata, windowdata); //cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, -0.46, windowdata, 1); //-0.46 * cos(2 * M_PI * i / size) for (int i = 0; i < size; i++) //0.54 - 0.46 * cos(2 * M_PI * i / size); { windowdata[i] += 0.54; } CG = 0.54; //海明窗的相干增益:0.54 ENBW = 1.36283; //海明窗的等效噪声宽度:1.36283 break; case WindowType.Blackman_Harris: // Blackman_Harris窗 //for (i = 0; i < size; i++)//产生单位窗函数 //{ // windowdata[i] = 0.42323 - 0.49755 * cos(2 * M_PI * i / size) + 0.07922 * cos(4 * M_PI * i / size);// Blackman_Harris窗公式:W[i]=0.42323-0.49755cos2iπ/N+0.07922cos4iπ/N; //} CBLASNative.cblas_dscal(size, pi2DSize, windowdata, 1); //2 * M_PI * i / size VMLNative.vdCos(size, windowdata, windowdata); //cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, -0.49755, windowdata, 1); //-0.49755 * cos(2 * M_PI * i / size) windowTmp = new double[size]; for (int i = 0; i < size; i++) { windowdata[i] += 0.42323; windowTmp[i] = i; } CBLASNative.cblas_dscal(size, pi4DSize, windowTmp, 1); //4 * M_PI * i / size VMLNative.vdCos(size, windowTmp, windowTmp); //cos(4 * M_PI * i / size) CBLASNative.cblas_dscal(size, 0.07922, windowTmp, 1); //0.07922 * cos(4 * M_PI * i / size) VMLNative.vdAdd(size, windowTmp, windowdata, windowdata); //0.42323 - 0.49755 * Math.Cos(2 * Math.PI * i / size) + 0.07922 * Math.Cos(4 * Math.PI * i / size); CG = 0.42323; // Blackman_Harris窗的相干增益:0.42323 ENBW = 1.708538; // Blackman_Harris窗的等效噪声宽度:1.708538 break; case WindowType.Exact_Blackman: //Exact Blackman窗 //for (i = 0; i < size; i++)//产生单位窗函数 //{ // windowdata[i] = 0.42659 - 0.49656 * cos(2 * M_PI * i / size) + 0.07684 * cos(4 * M_PI * i / size);//Exact Blackman窗公式:W[i]=7938/18608-(9240/18608)*cos2iπ/N+(1430/18608)*cos4iπ/N; //} CBLASNative.cblas_dscal(size, pi2DSize, windowdata, 1); //2 * M_PI * i / size VMLNative.vdCos(size, windowdata, windowdata); //cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, -0.49656, windowdata, 1); //-0.49656 * cos(2 * M_PI * i / size) windowTmp = new double[size]; for (int i = 0; i < size; i++) //0.42659 - 0.49656 * cos(2 * M_PI * i / size); { windowdata[i] += 0.42659; windowTmp[i] = i; } CBLASNative.cblas_dscal(size, pi4DSize, windowTmp, 1); //4 * M_PI * i / size VMLNative.vdCos(size, windowTmp, windowTmp); //cos(4 * M_PI * i / size) CBLASNative.cblas_dscal(size, 0.07684, windowTmp, 1); //0.07684 * cos(4 * M_PI * i / size) VMLNative.vdAdd(size, windowTmp, windowdata, windowdata); //0.42659 - 0.49656 * Math.Cos(2 * Math.PI * i / size) + 0.07684 * Math.Cos(4 * Math.PI * i / size); CG = 0.42659; //Exact Blackman窗的相干增益:0.42659 ENBW = 1.69369; //Exact Blackman窗的等效噪声宽度:1.69369 break; case WindowType.Blackman: //Blackman窗 //for (i = 0; i < size; i++)//产生单位窗函数 //{ // windowdata[i] = 0.42 - 0.5 * cos(2 * M_PI * i / size) + 0.08 * cos(4 * M_PI * i / size);//Blackman窗公式:W[i]=0.42-0.5cos2iπ/N+0.08cos4iπ/N; //} CBLASNative.cblas_dscal(size, pi2DSize, windowdata, 1); //2 * M_PI * i / size VMLNative.vdCos(size, windowdata, windowdata); //cos(2 * M_PI * i / size) CBLASNative.cblas_dscal(size, -0.5, windowdata, 1); //-0.5 * cos(2 * M_PI * i / size) windowTmp = new double[size]; for (int i = 0; i < size; i++) //0.42 - 0.5 * cos(2 * M_PI * i / size); { windowdata[i] += 0.42; windowTmp[i] = i; } CBLASNative.cblas_dscal(size, pi4DSize, windowTmp, 1); //4 * M_PI * i / size VMLNative.vdCos(size, windowTmp, windowTmp); //cos(4 * M_PI * i / size) CBLASNative.cblas_dscal(size, 0.08, windowTmp, 1); //0.08 * cos(4 * M_PI * i / size) VMLNative.vdAdd(size, windowTmp, windowdata, windowdata); //0.42 - 0.5 * Math.Cos(2 * Math.PI * i / size) + 0.08 * Math.Cos(4 * Math.PI * i / size); CG = 0.42; //Blackman窗的相干增益:0.42 ENBW = 1.72676; //Blackman窗的等效噪声宽度:1.72676 break; case WindowType.Flat_Top: //平顶窗 for (int i = 0; i < size; i++) //产生单位窗函数 { windowdata[i] = 0.21558 - 0.41663 * Math.Cos(2 * Math.PI * i / size) + 0.27726 * Math.Cos(4 * Math.PI * i / size) - 0.08358 * Math.Cos(6 * Math.PI * i / size) + 0.00695 * Math.Cos(8 * Math.PI * i / size); //平顶窗的公式:W(i)=0.21557895-0.41663158cos2iπ/N+0.277263158cos4iπ/N-0.083578947cos6iπ/N+0.006947368cos8iπ/N; } CG = 0.22; //平顶窗的相干增益:0.22 ENBW = 3.77; //平顶窗的等效噪声宽度:3.77 break; case WindowType.Four_Term_B_Harris: //4 Term B-Harris窗 for (int i = 0; i < size; i++) //产生单位窗函数 { windowdata[i] = 0.35875 - 0.48829 * Math.Cos(2 * Math.PI * i / size) + 0.14128 * Math.Cos(4 * Math.PI * i / size) - 0.01168 * Math.Cos(6 * Math.PI * i / size); //4 Term B-Harris窗的公式:W(i)=0.35875-0.48829cos2iπ/N+0.14128cos4iπ/N-0.01168cos6iπ/N; } CG = 0.35875; //4 Term B-Harris窗的相干增益:0.35875 ENBW = 2.00435; //4 Term B-Harris窗的等效噪声宽度:2.00435 break; //7 Term B-Harris窗 case WindowType.Seven_Term_B_Harris: for (int i = 0; i < size; i++) //产生单位窗函数 { windowdata[i] = 0.27105 - 0.43329793923448 * Math.Cos(1 * 2 * Math.PI * i / size) + 0.21812299954311 * Math.Cos(2 * 2 * Math.PI * i / size) - 0.06592544638803 * Math.Cos(3 * 2 * Math.PI * i / size) + 0.01081174209837 * Math.Cos(4 * 2 * Math.PI * i / size) - 0.00077658482522 * Math.Cos(5 * 2 * Math.PI * i / size) + 0.00001388721735 * Math.Cos(6 * 2 * Math.PI * i / size); } CG = 0.27105; //7 Term B-Harris窗的相干增益:0.27105140069 ENBW = 2.63191; //7 Term B-Harris窗的等效噪声宽度:2.631905 break; // //Low_sidelobe窗 //case Low_sidelobe://无 // //break; //other 默认矩形窗 //矩形窗 case WindowType.None: for (int i = 0; i < size; i++) //产生单位窗函数 { windowdata[i] = 1.0; } CG = 1.0; //矩形窗的相干增益:1.0 ENBW = 1.0; //矩形窗的等效噪声宽度:1.0 break; default: throw new JYDSPParamException("Window type is out of range!"); } }