Skip to content

Commit 8bd0320

Browse files
committed
Перенесены SDR-инструменты в MathCore.DSP и добавлен DEBUG FFT API
1 parent 00a88c0 commit 8bd0320

4 files changed

Lines changed: 816 additions & 338 deletions

File tree

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#nullable enable
2+
3+
using ComplexN = System.Numerics.Complex;
4+
5+
namespace MathCore.DSP.SDR;
6+
7+
/// <summary>Инструменты отладки спектра для пошагового анализа SDR-конвейера</summary>
8+
public static class SdrDebugSpectrum
9+
{
10+
/// <summary>Сформировать спектральный снимок комплексного сигнала для анализа в отладчике</summary>
11+
/// <param name="Samples">Массив комплексных отсчётов</param>
12+
/// <param name="SampleRateHz">Частота дискретизации сигнала</param>
13+
/// <param name="FftSize">Размер БПФ и размер выходных массивов</param>
14+
/// <param name="ApplyHannWindow">Признак применения окна Ханна перед БПФ</param>
15+
/// <returns>Кортеж из массива частот и массива уровней мощности в dB</returns>
16+
/// <exception cref="ArgumentNullException">Входной массив не задан</exception>
17+
/// <exception cref="ArgumentOutOfRangeException">Некорректно задана частота дискретизации или размер БПФ</exception>
18+
/// <exception cref="ArgumentException">Размер БПФ не является степенью двойки</exception>
19+
/// <remarks>
20+
/// Пример использования:
21+
/// <code><![CDATA[
22+
/// #if DEBUG
23+
/// var (freq_hz, power_db) = SdrDebugSpectrum.GetSpectrumSnapshot(iq_data, SampleRateHz: 10_000_000, FftSize: 32768);
24+
/// var peak = SdrDebugSpectrum.FindPeakInRange(freq_hz, power_db, 600_000, 1_000_000);
25+
/// Console.WriteLine($"FFT peak: f={peak.frequency_hz:F0} Hz, p={peak.power_db:F2} dB");
26+
/// #endif
27+
/// ]]></code>
28+
/// </remarks>
29+
public static (double[] frequency_hz, double[] power_db) GetSpectrumSnapshot(
30+
ComplexN[] Samples,
31+
double SampleRateHz,
32+
int FftSize = 8192,
33+
bool ApplyHannWindow = true)
34+
{
35+
ArgumentNullException.ThrowIfNull(Samples);
36+
37+
if (SampleRateHz <= 0)
38+
throw new ArgumentOutOfRangeException(nameof(SampleRateHz), SampleRateHz, "Частота дискретизации должна быть положительной");
39+
40+
if (FftSize <= 0)
41+
throw new ArgumentOutOfRangeException(nameof(FftSize), FftSize, "Размер БПФ должен быть положительным");
42+
43+
if ((FftSize & (FftSize - 1)) != 0)
44+
throw new ArgumentException("Размер БПФ должен быть степенью двойки", nameof(FftSize));
45+
46+
var buffer = new ComplexN[FftSize];
47+
var copy_count = Math.Min(FftSize, Samples.Length);
48+
49+
for (var index = 0; index < copy_count; index++)
50+
{
51+
var sample = Samples[index];
52+
if (!ApplyHannWindow)
53+
{
54+
buffer[index] = sample;
55+
continue;
56+
}
57+
58+
var window = 0.5 - 0.5 * Math.Cos(2 * Math.PI * index / (FftSize - 1));
59+
buffer[index] = sample * window;
60+
}
61+
62+
var spectrum = FastFft(buffer);
63+
var frequency = new double[FftSize];
64+
var power_db = new double[FftSize];
65+
66+
for (var index = 0; index < FftSize; index++)
67+
{
68+
var shifted = (index + FftSize / 2) % FftSize;
69+
var frequency_hz = (index - FftSize / 2) * SampleRateHz / FftSize;
70+
var magnitude = spectrum[shifted].Magnitude;
71+
var power = magnitude * magnitude;
72+
73+
frequency[index] = frequency_hz;
74+
power_db[index] = 10 * Math.Log10(Math.Max(power, 1e-18));
75+
}
76+
77+
return (frequency, power_db);
78+
}
79+
80+
/// <summary>Найти пик спектра в заданном частотном диапазоне</summary>
81+
/// <param name="FrequencyHz">Массив частотных бинов</param>
82+
/// <param name="PowerDb">Массив уровней мощности</param>
83+
/// <param name="FromHz">Левая граница диапазона поиска</param>
84+
/// <param name="ToHz">Правая граница диапазона поиска</param>
85+
/// <returns>Кортеж частоты и уровня найденного пика</returns>
86+
/// <exception cref="ArgumentNullException">Один из входных массивов не задан</exception>
87+
/// <exception cref="ArgumentException">Размеры массивов не совпадают</exception>
88+
/// <remarks>
89+
/// Пример использования:
90+
/// <code><![CDATA[
91+
/// var peak_zero = SdrDebugSpectrum.FindPeakInRange(freq_hz, power_db, -100_000, 100_000);
92+
/// var peak_rf = SdrDebugSpectrum.FindPeakInRange(freq_hz, power_db, 600_000, 1_000_000);
93+
/// ]]></code>
94+
/// </remarks>
95+
public static (double frequency_hz, double power_db) FindPeakInRange(double[] FrequencyHz, double[] PowerDb, double FromHz, double ToHz)
96+
{
97+
ArgumentNullException.ThrowIfNull(FrequencyHz);
98+
ArgumentNullException.ThrowIfNull(PowerDb);
99+
100+
if (FrequencyHz.Length != PowerDb.Length)
101+
throw new ArgumentException("Массивы частот и мощности должны иметь одинаковую длину");
102+
103+
if (FrequencyHz.Length == 0)
104+
return (0, double.NegativeInfinity);
105+
106+
var left = Math.Min(FromHz, ToHz);
107+
var right = Math.Max(FromHz, ToHz);
108+
109+
var best_index = -1;
110+
var best_power = double.NegativeInfinity;
111+
112+
for (var index = 0; index < FrequencyHz.Length; index++)
113+
{
114+
var frequency = FrequencyHz[index];
115+
if (frequency < left || frequency > right)
116+
continue;
117+
118+
var power = PowerDb[index];
119+
if (power <= best_power)
120+
continue;
121+
122+
best_power = power;
123+
best_index = index;
124+
}
125+
126+
if (best_index < 0)
127+
return (0, double.NegativeInfinity);
128+
129+
return (FrequencyHz[best_index], PowerDb[best_index]);
130+
}
131+
132+
private static ComplexN[] FastFft(ComplexN[] Data)
133+
{
134+
var n = Data.Length;
135+
var result = new ComplexN[n];
136+
Array.Copy(Data, result, n);
137+
138+
var j = 0;
139+
for (var i = 1; i < n; i++)
140+
{
141+
var bit = n >> 1;
142+
while ((j & bit) != 0)
143+
{
144+
j &= ~bit;
145+
bit >>= 1;
146+
}
147+
148+
j |= bit;
149+
150+
if (i < j)
151+
(result[i], result[j]) = (result[j], result[i]);
152+
}
153+
154+
for (var len = 2; len <= n; len <<= 1)
155+
{
156+
var angle = -2 * Math.PI / len;
157+
var w_len = new ComplexN(Math.Cos(angle), Math.Sin(angle));
158+
159+
for (var i = 0; i < n; i += len)
160+
{
161+
var w = ComplexN.One;
162+
var half_len = len / 2;
163+
for (var k = 0; k < half_len; k++)
164+
{
165+
var u = result[i + k];
166+
var v = result[i + k + half_len] * w;
167+
168+
result[i + k] = u + v;
169+
result[i + k + half_len] = u - v;
170+
171+
w *= w_len;
172+
}
173+
}
174+
}
175+
176+
return result;
177+
}
178+
}

0 commit comments

Comments
 (0)