Skip to content

Commit 09a614b

Browse files
committed
Фазовая модуляция
1 parent 6f715fc commit 09a614b

4 files changed

Lines changed: 558 additions & 65 deletions

File tree

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using MathCore.DSP.Samples;
2+
using MathCore.DSP.Samples.Extensions;
3+
4+
namespace MathCore.DSP.Examples;
5+
6+
/// <summary>Примеры использования фазовой модуляции и демодуляции</summary>
7+
public static class PhaseModulationExamples
8+
{
9+
/// <summary>Демонстрация базового круглого преобразования</summary>
10+
public static void BasicRoundTripExample()
11+
{
12+
Console.WriteLine("=== Базовый пример круглого преобразования ===");
13+
14+
// Исходные данные для передачи (отклонения частоты в Гц)
15+
var original_data = new float[] { 0f, 200f, -300f, 150f, -100f, 0f };
16+
17+
const double f0 = 2400e6; // 2.4 ГГц центральная частота
18+
const double fd = 48000.0; // 48 кГц дискретизация
19+
20+
Console.WriteLine("Исходные данные (отклонения частоты, Гц):");
21+
Console.WriteLine($"[{string.Join(", ", original_data.Select(x => x.ToString("F0")))}]");
22+
23+
// Фазовая модуляция
24+
var modulated_samples = original_data.AsSpan().PhaseModulation(f0, fd, amplitude: 120f);
25+
Console.WriteLine($"\nМодулировано {modulated_samples.Length} I/Q отсчётов");
26+
27+
// Фазовая демодуляция
28+
var demodulated_data = modulated_samples.AsSpan().PhaseDemodulation(f0, fd);
29+
Console.WriteLine($"Демодулировано {demodulated_data.Length} значений");
30+
31+
// Сравнение результатов (пропускаем первые отсчёты из-за переходных процессов)
32+
Console.WriteLine("\nСравнение исходных и демодулированных данных:");
33+
Console.WriteLine("Индекс | Исходные | Демодул. | Ошибка");
34+
Console.WriteLine("-------|----------|----------|--------");
35+
36+
for (var i = 2; i < Math.Min(original_data.Length, demodulated_data.Length); i++)
37+
{
38+
var error = Math.Abs(demodulated_data[i] - original_data[i]);
39+
Console.WriteLine($"{i,6} | {original_data[i],8:F1} | {demodulated_data[i],8:F1} | {error,6:F1}");
40+
}
41+
}
42+
43+
/// <summary>Демонстрация непрерывной модуляции блоками</summary>
44+
public static void ContinuousBlockModulationExample()
45+
{
46+
Console.WriteLine("\n=== Пример непрерывной модуляции блоками ===");
47+
48+
// Разбиваем данные на блоки
49+
var data_blocks = new[]
50+
{
51+
new float[] { 0f, 100f, 200f },
52+
new float[] { 300f, 200f, 100f },
53+
new float[] { 0f, -100f, -200f }
54+
};
55+
56+
const double f0 = 915e6; // 915 МГц ISM диапазон
57+
const double fd = 2e6; // 2 МГц дискретизация
58+
59+
var all_samples = new List<SampleSI16>();
60+
var phase = 0.0; // Начальная фаза
61+
62+
for (var block_index = 0; block_index < data_blocks.Length; block_index++)
63+
{
64+
var data_block = data_blocks[block_index];
65+
Console.WriteLine($"\nБлок {block_index + 1}: [{string.Join(", ", data_block.Select(x => x.ToString("F0")))}] Гц");
66+
Console.WriteLine($"Начальная фаза: {phase:F3} рад");
67+
68+
// Модуляция с сохранением фазы
69+
var (samples, final_phase) = data_block.AsSpan().PhaseModulation(f0, fd, phase);
70+
71+
Console.WriteLine($"Финальная фаза: {final_phase:F3} рад");
72+
Console.WriteLine($"Сгенерировано {samples.Length} I/Q отсчётов");
73+
74+
// Добавляем к общему потоку
75+
all_samples.AddRange(samples);
76+
77+
// Продолжаем с сохранением фазы
78+
phase = final_phase;
79+
}
80+
81+
Console.WriteLine($"\nВсего сгенерировано {all_samples.Count} I/Q отсчётов");
82+
83+
// Демодуляция всего потока
84+
var demodulated = all_samples.ToArray().AsSpan().PhaseDemodulation(f0, fd);
85+
Console.WriteLine($"Демодулировано {demodulated.Length} значений");
86+
87+
// Показываем восстановленные данные
88+
Console.WriteLine("\nВосстановленные данные (пропуская переходные процессы):");
89+
var flat_original = data_blocks.SelectMany(x => x).ToArray();
90+
91+
for (var i = 3; i < Math.Min(flat_original.Length, demodulated.Length - 1); i++)
92+
{
93+
var error = Math.Abs(demodulated[i] - flat_original[i]);
94+
Console.WriteLine($"[{i}] Исходные: {flat_original[i]:F0} Гц, " +
95+
$"Демодул.: {demodulated[i]:F1} Гц, " +
96+
$"Ошибка: {error:F1} Гц");
97+
}
98+
}
99+
100+
/// <summary>Тест производительности модуляции и демодуляции</summary>
101+
public static void PerformanceTest()
102+
{
103+
Console.WriteLine("\n=== Тест производительности ===");
104+
105+
const int data_count = 1_000_000;
106+
const double f0 = 2.4e9;
107+
const double fd = 48000.0;
108+
109+
// Генерируем тестовые данные
110+
var random = new Random(42);
111+
var test_data = new float[data_count];
112+
for (var i = 0; i < data_count; i++)
113+
test_data[i] = (float)(random.NextDouble() * 1000 - 500); // ±500 Гц
114+
115+
Console.WriteLine($"Тестирование на {data_count:N0} образцах");
116+
117+
// Тест модуляции
118+
var modulation_timer = System.Diagnostics.Stopwatch.StartNew();
119+
var modulated = test_data.AsSpan().PhaseModulation(f0, fd);
120+
modulation_timer.Stop();
121+
122+
var modulation_rate = data_count / modulation_timer.Elapsed.TotalSeconds / 1e6;
123+
Console.WriteLine($"Модуляция: {modulation_timer.ElapsedMilliseconds} мс, " +
124+
$"{modulation_rate:F1} млн. образцов/сек");
125+
126+
// Тест демодуляции
127+
var demodulation_timer = System.Diagnostics.Stopwatch.StartNew();
128+
var demodulated = modulated.AsSpan().PhaseDemodulation(f0, fd);
129+
demodulation_timer.Stop();
130+
131+
var demodulation_rate = data_count / demodulation_timer.Elapsed.TotalSeconds / 1e6;
132+
Console.WriteLine($"Демодуляция: {demodulation_timer.ElapsedMilliseconds} мс, " +
133+
$"{demodulation_rate:F1} млн. образцов/сек");
134+
135+
// Проверка точности
136+
var errors = new List<float>();
137+
for (var i = 10; i < data_count - 10; i++) // Пропускаем края
138+
{
139+
var error = Math.Abs(demodulated[i] - test_data[i]);
140+
errors.Add(error);
141+
}
142+
143+
var avg_error = errors.Average();
144+
var max_error = errors.Max();
145+
146+
Console.WriteLine($"Средняя ошибка: {avg_error:F2} Гц");
147+
Console.WriteLine($"Максимальная ошибка: {max_error:F2} Гц");
148+
Console.WriteLine($"Точность: {100 * (1 - avg_error / 500):F1}%");
149+
}
150+
151+
/// <summary>Запуск всех примеров</summary>
152+
public static void RunAllExamples()
153+
{
154+
Console.WriteLine("Примеры использования фазовой модуляции и демодуляции");
155+
Console.WriteLine("=====================================================");
156+
157+
BasicRoundTripExample();
158+
ContinuousBlockModulationExample();
159+
PerformanceTest();
160+
161+
Console.WriteLine("\n=== Примеры завершены ===");
162+
}
163+
}

MathCore.DSP/Samples/Extensions/SampleSI16Ex.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,96 @@ public static float[] PhaseDemodulation(this Span<SampleSI16> samples, double f0
5252
return result;
5353
}
5454

55+
/// <summary>Фазовая модуляция сигнала для передачи</summary>
56+
/// <param name="data">Модулирующие данные (мгновенные частоты в Гц)</param>
57+
/// <param name="f0">Центральная частота передачи</param>
58+
/// <param name="fd">Частота дискретизации</param>
59+
/// <param name="amplitude">Амплитуда выходного сигнала (по умолчанию 120 для максимального использования динамического диапазона)</param>
60+
/// <returns>Массив квадратурных отсчётов для передачи</returns>
61+
public static SampleSI16[] PhaseModulation(this ReadOnlySpan<float> data, double f0, double fd, float amplitude = 120f)
62+
{
63+
if (data.IsEmpty) return [];
64+
65+
var result = new SampleSI16[data.Length];
66+
var dt = 1.0 / fd;
67+
var omega0 = 2.0 * Math.PI * f0;
68+
var accumulated_phase = 0.0;
69+
70+
for (var i = 0; i < data.Length; i++)
71+
{
72+
// Мгновенная частота = f0 + данные[i]
73+
var instantaneous_frequency = f0 + data[i];
74+
75+
// Интегрируем частоту для получения фазы: φ(t) = ∫ω(t)dt
76+
var omega_instant = 2.0 * Math.PI * instantaneous_frequency;
77+
accumulated_phase += omega_instant * dt;
78+
79+
// Генерируем квадратурный сигнал: I = A*cos(φ), Q = A*sin(φ)
80+
#if NET8_0_OR_GREATER
81+
var cos_phase = MathF.Cos((float)accumulated_phase);
82+
var sin_phase = MathF.Sin((float)accumulated_phase);
83+
#else
84+
var cos_phase = (float)Math.Cos(accumulated_phase);
85+
var sin_phase = (float)Math.Sin(accumulated_phase);
86+
#endif
87+
88+
// Масштабируем и ограничиваем в диапазоне sbyte
89+
var i_sample = ClampToSByte(amplitude * cos_phase);
90+
var q_sample = ClampToSByte(amplitude * sin_phase);
91+
92+
result[i] = new SampleSI16(i_sample, q_sample);
93+
}
94+
95+
return result;
96+
}
97+
98+
/// <summary>Фазовая модуляция с опорным сигналом для непрерывности фазы</summary>
99+
/// <param name="data">Модулирующие данные (мгновенные частоты в Гц)</param>
100+
/// <param name="f0">Центральная частота передачи</param>
101+
/// <param name="fd">Частота дискретизации</param>
102+
/// <param name="initial_phase">Начальная фаза для обеспечения непрерывности</param>
103+
/// <param name="amplitude">Амплитуда выходного сигнала</param>
104+
/// <returns>Массив квадратурных отсчётов и финальная фаза для следующего блока</returns>
105+
public static (SampleSI16[] samples, double final_phase) PhaseModulation(
106+
this ReadOnlySpan<float> data,
107+
double f0,
108+
double fd,
109+
double initial_phase,
110+
float amplitude = 120f)
111+
{
112+
if (data.IsEmpty) return ([], initial_phase);
113+
114+
var result = new SampleSI16[data.Length];
115+
var dt = 1.0 / fd;
116+
var accumulated_phase = initial_phase;
117+
118+
for (var i = 0; i < data.Length; i++)
119+
{
120+
// Мгновенная частота = f0 + данные[i]
121+
var instantaneous_frequency = f0 + data[i];
122+
123+
// Интегрируем частоту для получения фазы
124+
var omega_instant = 2.0 * Math.PI * instantaneous_frequency;
125+
accumulated_phase += omega_instant * dt;
126+
127+
// Генерируем квадратурный сигнал
128+
#if NET8_0_OR_GREATER
129+
var cos_phase = MathF.Cos((float)accumulated_phase);
130+
var sin_phase = MathF.Sin((float)accumulated_phase);
131+
#else
132+
var cos_phase = (float)Math.Cos(accumulated_phase);
133+
var sin_phase = (float)Math.Sin(accumulated_phase);
134+
#endif
135+
136+
var i_sample = ClampToSByte(amplitude * cos_phase);
137+
var q_sample = ClampToSByte(amplitude * sin_phase);
138+
139+
result[i] = new SampleSI16(i_sample, q_sample);
140+
}
141+
142+
return (result, accumulated_phase);
143+
}
144+
55145
/// <summary>Unwrapping одной разности фаз с накоплением коррекции</summary>
56146
[MethodImpl(MethodImplOptions.AggressiveInlining)]
57147
private static float UnwrapSinglePhaseDiff(float phase_diff, ref float accumulated_correction)
@@ -77,4 +167,14 @@ private static float UnwrapSinglePhaseDiff(float phase_diff, ref float accumulat
77167

78168
return phase_diff;
79169
}
170+
171+
/// <summary>Ограничивает значение в диапазоне sbyte с оптимальным использованием динамического диапазона</summary>
172+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
173+
private static sbyte ClampToSByte(float value) =>
174+
value switch // Ограничиваем в диапазоне [-127, 127] для избежания переполнения
175+
{
176+
> 127f => 127,
177+
< -128f => -128,
178+
_ => (sbyte)Math.Round(value)
179+
};
80180
}

0 commit comments

Comments
 (0)