mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +00:00
chore: refine project documentation
This commit is contained in:
+40
-44
@@ -196,7 +196,7 @@ const
|
||||
TX_MIC_RING = 8192;
|
||||
|
||||
// TX/DUC sample rate. Фиксирован независимо от FSampleRate (RX).
|
||||
// Thetis/piHPSDR держат TX rate постоянным (192k для Hermes/Saturn DUC0),
|
||||
// TX rate постоянен: 192k для Hermes/Saturn DUC0,
|
||||
// меняя только RX rate. Это гарантирует, что DUC IQ всегда подаётся в том
|
||||
// темпе, который ожидает железо (DUC0RateHi/Lo в DUC Specific = 192).
|
||||
TX_SAMPLE_RATE = 192000;
|
||||
@@ -323,7 +323,7 @@ type
|
||||
|
||||
// Копия входных IQ для Spectrum0 (до fexchange0, который данные in-place перезаписывает)
|
||||
FSpecBuf: array of Double;
|
||||
// Отдельный мелкий feed для display-анализатора, как в Thetis spec_blocksize.
|
||||
// Отдельный небольшой feed для display-анализатора.
|
||||
FDispBuf: array of Double;
|
||||
FDispPos: Integer;
|
||||
// То же самое для TX-analyzer: TX_DISP_ID получает сэмплы чанками
|
||||
@@ -372,7 +372,7 @@ type
|
||||
FWfAvgMode: Integer;
|
||||
FWfAvgTimeMS: Double;
|
||||
|
||||
// Zoom/pan анализатора (span-clip в SetAnalyzer). Конвенция WDSP/Thetis:
|
||||
// Zoom/pan анализатора (span-clip в SetAnalyzer). Конвенция WDSP:
|
||||
// FZoomFactor 0.0..1.0, где 0.0 = весь span (без зума), 1.0 = максимум.
|
||||
// FPanSlider 0..1 (положение окна по span). FViewLowHz/FViewHighHz —
|
||||
// границы видимого окна в Гц относительно DDC-центра (low отрицательный),
|
||||
@@ -531,7 +531,7 @@ type
|
||||
FFilterLow: Integer;
|
||||
FFilterHigh: Integer;
|
||||
FAGCMode: TWDSPAGCMode;
|
||||
FAGCTop: Double; // gain (dB) = agc_gain в piHPSDR
|
||||
FAGCTop: Double; // AGC gain in dB
|
||||
FAGCSlope: Integer; // наклон АРУ в дБ (default 0)
|
||||
FAGCHangThreshold: Integer; // порог hang 0..100
|
||||
FAGCHangLevel: Double; // читается из WDSP (GetRXAAGCHangLevel)
|
||||
@@ -580,7 +580,7 @@ type
|
||||
// главного RXA и для слайсов — иначе дублирование по трём местам.
|
||||
procedure ApplyRXModeSettings(Chan, Mode: Integer);
|
||||
// Магнитуда TX-полосы (FTXFilter*) → знаковые края bp0 по боковой режима.
|
||||
// Порт Thetis UpdateTXLowHighFilterForMode: единственный конвертер знака,
|
||||
// Единственный конвертер знака краёв TX-фильтра для выбранного режима,
|
||||
// через который обязан идти ЛЮБОЙ пуш TX-bandpass, иначе LSB уходит в USB.
|
||||
procedure TXSignedEdges(Mode: Integer; out Lo, Hi: Double);
|
||||
// --- CW pitch -----------------------------------------------------------
|
||||
@@ -783,7 +783,7 @@ type
|
||||
procedure SetTXTone(Enabled: Boolean; FreqHz, Mag: Double);
|
||||
// 2TON: PostGen two-tone (IMD-тест / калибровка PureSignal).
|
||||
// Тона Freq1/Freq2 Гц (для LSB/CWL/DIGL знак инвертируется), амплитуды
|
||||
// по 0.49 (сумма чуть ниже полной шкалы, как Thetis/pihpsdr).
|
||||
// по 0.49, чтобы сумма оставалась чуть ниже полной шкалы.
|
||||
procedure SetTXTwoTone(Enabled: Boolean; Freq1, Freq2: Double);
|
||||
// Protocol 2: компенсирующий FIR перед FPGA CIC-интерполятором TX.
|
||||
// Для openHPSDR P2 всегда включён; для остальных бэкендов выключен.
|
||||
@@ -795,7 +795,7 @@ type
|
||||
procedure SetPureSignalArmed(Armed: Boolean);
|
||||
// Feedback-пакет синхронизированного DDC0 (interleaved: пара DDC0 =
|
||||
// RX-feedback, пара DDC1 = TX DAC, 24-bit BE). Вызывается из СЕТЕВОГО
|
||||
// потока (как Thetis/pihpsdr — pscc потокобезопасен относительно
|
||||
// потока; pscc потокобезопасен относительно
|
||||
// fexchange0 TX-потока). TotalPairs — всего IQ-пар в пакете (оба потока).
|
||||
procedure PushPSFeedback(const Buf: array of Byte; DataOffset: Integer;
|
||||
TotalPairs: Integer);
|
||||
@@ -958,7 +958,7 @@ implementation
|
||||
// ===========================================================================
|
||||
// TX DSP поток — читает mic ring buffer, вызывает fexchange0(TXA_CHAN),
|
||||
// сигнализирует о готовых IQ данных через FOnTXIQ callback.
|
||||
// Аналог tx_thread в piHPSDR/transmitter.c
|
||||
// Поток обработки TX-аудио и формирования IQ.
|
||||
// ===========================================================================
|
||||
type
|
||||
TDisplayThread = class(TThread)
|
||||
@@ -981,7 +981,7 @@ type
|
||||
|
||||
function UsePanNormOneHz(Detector: Integer): Integer;
|
||||
begin
|
||||
// В Thetis нормализация к 1 Гц включается только для panadapter
|
||||
// Нормализация к 1 Гц включается только для panadapter
|
||||
// и только для detector modes 2/3/4.
|
||||
if Detector in [2, 3, 4] then
|
||||
Result := 1
|
||||
@@ -1029,8 +1029,7 @@ begin
|
||||
end;
|
||||
|
||||
procedure TTXDSPThread.Execute;
|
||||
// Аналог tx_thread в piHPSDR/transmitter.c:
|
||||
// Тикает с периодом одного mic-буфера (FAudioBufSize/FAudioRate).
|
||||
// Поток работает с периодом одного mic-буфера (FAudioBufSize/FAudioRate).
|
||||
// Если mic-данные накопились — обрабатывает их; если нет — обрабатывает тишину.
|
||||
// Это обеспечивает непрерывный поток DUC IQ к железу и TX-сигнал на спектре/водопаде.
|
||||
var
|
||||
@@ -1052,7 +1051,7 @@ begin
|
||||
Dec(AccUs, PeriodMs * 1000);
|
||||
|
||||
// Ждём сигнала от mic-данных ИЛИ истечения периода —
|
||||
// в piHPSDR timing идёт от receive thread; здесь — от таймаута
|
||||
// Здесь timing задаётся таймаутом, синхронизированным с размером буфера.
|
||||
RTLEventWaitFor(FEngine.FTXMicSem, PeriodMs);
|
||||
if Terminated then Break;
|
||||
if not FEngine.FTXActive then Continue;
|
||||
@@ -1085,7 +1084,7 @@ begin
|
||||
|
||||
if Terminated then Break;
|
||||
|
||||
// Silence-инъекция УБРАНА (модель dl1ycf pihpsdr). Раньше при пустом ring
|
||||
// Silence-инъекция УБРАНА. Раньше при пустом ring
|
||||
// (>6 тиков) сюда впрыскивался блок тишины «чтобы DUC IQ не прерывался».
|
||||
// Но это фабрикация данных не на радиоклоке: для TUN такой «silence» несёт
|
||||
// PostGen-тон, и лишний блок ломает темп DUC IQ (боковые/тр-р-р); плюс он
|
||||
@@ -1098,9 +1097,8 @@ begin
|
||||
end;
|
||||
|
||||
// ===========================================================================
|
||||
// DSP поток — обрабатывает IQ пакеты из очереди
|
||||
// Сетевой поток только кладёт пакеты, этот поток занимается DSP
|
||||
// Аналог iq_thread в piHPSDR/new_protocol.c
|
||||
// DSP поток обрабатывает IQ-пакеты из очереди.
|
||||
// Сетевой поток только кладёт пакеты, этот поток занимается DSP.
|
||||
// ===========================================================================
|
||||
type
|
||||
TDSPThread = class(TThread)
|
||||
@@ -1125,7 +1123,7 @@ var
|
||||
begin
|
||||
while not Terminated do
|
||||
begin
|
||||
// Ждём сигнала от сетевого потока (как sem_wait в piHPSDR)
|
||||
// Ждём сигнала от сетевого потока.
|
||||
RTLEventWaitFor(FEngine.FQueueSem, 100);
|
||||
if Terminated then Break;
|
||||
|
||||
@@ -1339,7 +1337,7 @@ begin
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.TXSignedEdges(Mode: Integer; out Lo, Hi: Double);
|
||||
// Порт Thetis UpdateTXLowHighFilterForMode. Вход — магнитуда TX-аудио-полосы
|
||||
// Вход — магнитуда TX-аудио-полосы
|
||||
// (FTXFilterLow/High, всегда положительная); выход — знаковые края bp0 под
|
||||
// боковую режима. Нижняя боковая зеркалит полосу в отрицательные частоты.
|
||||
var
|
||||
@@ -1376,8 +1374,7 @@ begin
|
||||
end;
|
||||
|
||||
function TWDSPEngine.CWLOOffset(Mode: Integer): Double;
|
||||
// Знак — как в Thetis (console.cs:31757 rx_freq += pitch для CWL) и pihpsdr
|
||||
// (new_protocol.c:773 rxFrequency -= pitch для CWU).
|
||||
// Для CWL смещение положительное, для CWU — отрицательное.
|
||||
begin
|
||||
case Mode of
|
||||
MODE_CWL: Result := FCWPitch;
|
||||
@@ -1396,7 +1393,7 @@ end;
|
||||
procedure TWDSPEngine.PushPassband(Chan, Lo, Hi, Mode, Rate: Integer);
|
||||
// Кромки хранятся относительно VFO (у CW — симметрично нулю: «500 Гц вокруг
|
||||
// корреспондента»). В baseband демодулятора они уезжают навстречу гетеродину,
|
||||
// т.е. у CWU получается pitch±bw/2 — ровно таблица Thetis (console.cs:5338).
|
||||
// т.е. у CWU получается pitch±bw/2.
|
||||
var Off: Integer;
|
||||
begin
|
||||
Off := Round(CWLOOffset(Mode));
|
||||
@@ -1428,7 +1425,7 @@ begin
|
||||
begin
|
||||
// Restore the user's normal voice/data chain after leaving FM RAW.
|
||||
// bp1 (aux bandpass) НЕ трогаем вручную — им управляет компрессор через
|
||||
// TXASetupBPFilters (как в Thetis). bp0 всегда активен сам.
|
||||
// bp0 всегда активен сам; здесь обновляем остальную цепочку TX.
|
||||
ApplyTXChainSettings;
|
||||
if Mode = MODE_WFM then ApplyTXWFMSettings;
|
||||
end;
|
||||
@@ -1478,9 +1475,9 @@ begin
|
||||
inherited Create;
|
||||
FSampleRate := SampleRate;
|
||||
FAudioRate := AudioRate;
|
||||
// BufSize = dsp_size @ AudioRate (внутренний DSP буфер, piHPSDR buffer_size = 1024)
|
||||
// BufSize = dsp_size @ AudioRate; внутренний DSP buffer_size = 1024.
|
||||
// FBufSize = in_size @ SampleRate = BufSize * (SampleRate/AudioRate)
|
||||
// piHPSDR: in_size = buffer_size * sample_rate/48000 = 1024*4 = 4096 @ 192kHz
|
||||
// in_size = buffer_size * sample_rate/48000 = 1024*4 = 4096 @ 192kHz.
|
||||
FAudioBufSize := BufSize; // dsp_size = out_size @ AudioRate
|
||||
FBufSize := BufSize * SampleRate div AudioRate; // in_size @ SampleRate = 4096
|
||||
// TX rate фиксирован — не зависит от RX. FTXOutBufSize = out_size TXA @ FTXSampleRate.
|
||||
@@ -1579,7 +1576,7 @@ begin
|
||||
FTXToneOn := False;
|
||||
FTXTTOn := False;
|
||||
FKeepRXDuringTX := False;
|
||||
// PureSignal — дефолты как Thetis PSForm / clsHardwareSpecific (P2)
|
||||
// PureSignal — значения по умолчанию для протокола 2.
|
||||
FPSArmed := False;
|
||||
FPSMoxDelay := 0.2; // с — задержка после MOX до сбора данных
|
||||
FPSLoopDelay := 0.0; // с — пауза между итерациями калибровки
|
||||
@@ -2210,7 +2207,7 @@ begin
|
||||
end;
|
||||
|
||||
try
|
||||
// Анализатор ПЕРВЫМ — до OpenChannel (порядок как в piHPSDR)
|
||||
// Анализатор открывается перед DSP-каналом.
|
||||
OpenAnalyzer;
|
||||
if not FAnalyzerOpen then
|
||||
begin
|
||||
@@ -2237,7 +2234,7 @@ begin
|
||||
0,
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
1 // bfo=1: fexchange0 БЛОКИРУЕТСЯ до готовности выходного блока (как
|
||||
// pihpsdr). bfo=0 давал async-чтение кольца r2 со сдвигом outidx →
|
||||
// bfo=0 давал async-чтение кольца r2 со сдвигом outidx →
|
||||
// изредка фазовый разрыв PostGen-тона (всплеск на водопаде + щелчок).
|
||||
);
|
||||
|
||||
@@ -2277,7 +2274,7 @@ begin
|
||||
Result := True;
|
||||
|
||||
// Запускаем DSP поток после успешной инициализации WDSP
|
||||
// Аналог iq_thread_id = g_thread_new("iq thread", ...) в piHPSDR
|
||||
// Запускаем отдельный поток обработки IQ.
|
||||
FDSPThread := TDSPThread.Create(Self);
|
||||
FDSPThread.Priority := tpHighest; // DSP критический поток
|
||||
|
||||
@@ -2531,7 +2528,7 @@ begin
|
||||
Item^.PanId := 0;
|
||||
|
||||
FQueueHead := NextHead;
|
||||
RTLEventSetEvent(FQueueSem); // будим DSP поток (как sem_post в piHPSDR)
|
||||
RTLEventSetEvent(FQueueSem); // будим DSP-поток
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushPanIQ(PanId: Integer; const Buf: array of Byte;
|
||||
@@ -2815,7 +2812,7 @@ begin
|
||||
FSMeter := GetRXAMeter(RXA_CHAN, RXA_S_AV);
|
||||
|
||||
// Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1).
|
||||
// При TX RX-аудио глушим (как в Thetis/piHPSDR при не-duplex MOX),
|
||||
// При TX RX-аудио глушим в режиме non-duplex MOX,
|
||||
// иначе оператор слышит свой эфир через RX-цепь и получает feedback.
|
||||
// Дополнительный гейт FPostTXMuteUntil закрывает «хвост» TX leakage
|
||||
// на Windows (radio FPGA TX-buffer + PA slew-down). Cheap-path: проверка
|
||||
@@ -2824,7 +2821,7 @@ begin
|
||||
FPostTXMuteUntil := 0;
|
||||
// Full-duplex self-monitor (QO-100): при FMonitorRXDuringTX не глушим RX-аудио
|
||||
// на TX — слышим свой downlink со спутника. Иначе (по умолчанию) глушим, как
|
||||
// Thetis/piHPSDR при не-duplex MOX. FPostTXMuteUntil-хвост уважаем в любом случае.
|
||||
// FPostTXMuteUntil-хвост уважаем и при non-duplex MOX.
|
||||
if (not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX))
|
||||
and (FPostTXMuteUntil = 0) then
|
||||
begin
|
||||
@@ -3523,7 +3520,7 @@ begin
|
||||
FAGCMode := Mode;
|
||||
if not FInitialized then Exit;
|
||||
|
||||
// Точно по piHPSDR receiver.c: set_agc()
|
||||
// Настройка параметров AGC приёмного канала.
|
||||
ApplyAGCToChan(RXA_CHAN, Mode, FixedGain);
|
||||
|
||||
// Читаем обратно hang level и thresh для линии на спектре
|
||||
@@ -3976,7 +3973,7 @@ end;
|
||||
|
||||
procedure TWDSPEngine.SetTXTwoTone(Enabled: Boolean; Freq1, Freq2: Double);
|
||||
// 2TON: двухтональник PostGen (mode 1) для IMD-теста и проверки PureSignal.
|
||||
// Амплитуды 0.49+0.49 — сумма чуть ниже полной шкалы (как Thetis/pihpsdr).
|
||||
// Амплитуды 0.49+0.49 дают сумму чуть ниже полной шкалы.
|
||||
var
|
||||
F1, F2: Double;
|
||||
begin
|
||||
@@ -4026,9 +4023,8 @@ end;
|
||||
procedure TWDSPEngine.ProcessTXBlock;
|
||||
// Вызывается из TTXDSPThread — обрабатывает один блок FAudioBufSize mic сэмплов
|
||||
// через WDSP TXA, выдаёт FTXOutBufSize IQ пар @ FTXSampleRate в FOnTXIQ callback.
|
||||
// Аналог tx_full_buffer() в piHPSDR/transmitter.c:
|
||||
// если mic ring buffer содержит достаточно данных — берём их,
|
||||
// иначе обрабатываем тишину (нули), как piHPSDR при mic_sample=0.0.
|
||||
// Если mic ring buffer содержит достаточно данных — берём их,
|
||||
// иначе обрабатываем тишину (нули).
|
||||
var
|
||||
i, tail: Integer;
|
||||
Err: Integer;
|
||||
@@ -4055,7 +4051,7 @@ begin
|
||||
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
||||
end else
|
||||
begin
|
||||
// Нет mic-данных — тишина (аналог piHPSDR: tx->mic_input_buffer заполняется нулями)
|
||||
// Нет mic-данных — заполняем входной TX-буфер нулями.
|
||||
FillChar(FTXIn[0], FAudioBufSize * 2 * SizeOf(Double), 0);
|
||||
end;
|
||||
|
||||
@@ -4258,7 +4254,7 @@ end;
|
||||
procedure TWDSPEngine.PushPSFeedback(const Buf: array of Byte;
|
||||
DataOffset: Integer; TotalPairs: Integer);
|
||||
// Сетевой поток: interleaved 24-bit BE пары [DDC0 I,Q][DDC1 I,Q]... →
|
||||
// деинтерливим и кормим WDSP pscc (как pihpsdr process_ps_iq_data):
|
||||
// Деинтерливим feedback-потоки и передаём их в WDSP pscc:
|
||||
// DDC0 = RX-feedback (искажённый сигнал после PA), DDC1 = TX DAC (эталон).
|
||||
var
|
||||
i, n, Pos: Integer;
|
||||
@@ -4329,7 +4325,7 @@ procedure TWDSPEngine.TXFilterEdgesHz(out Lo, Hi: Integer);
|
||||
// WFM (20..15000), и полоса на спектре уезжала вверх от несущей — визуально
|
||||
// «переключалась на USB». Для ЧМ считаем по Карсону: ±(девиация + верхний срез
|
||||
// аудио), симметрично несущей. У MODE_FM TXSignedEdges отдаёт ровно эту
|
||||
// величину и без нас (порт Thetis), но пусть формула будет в одном месте.
|
||||
// величину и без нас, но формула остаётся в одном месте.
|
||||
var L, H, Half: Double;
|
||||
begin
|
||||
Half := 0.0;
|
||||
@@ -4423,7 +4419,7 @@ end;
|
||||
|
||||
procedure TWDSPEngine.PushTXEQProfile;
|
||||
// Единственная точка, отдающая кривую EQ в WDSP.
|
||||
// F[1..n] — УЗЛЫ ломаной АЧХ (eq.c/eq_impulse), между ними интерполяция по
|
||||
// F[1..n] — узлы ломаной АЧХ, между ними интерполяция по
|
||||
// децибелам, а за крайними узлами при ctfmode=0 (так создаётся eqp в TXA.c)
|
||||
// идёт кумулятивный скат (f/f0)^4 на бин — фактически обрыв. Поэтому:
|
||||
// • 10 полос — узлы берём из профиля как есть (за краями обязан быть запас,
|
||||
@@ -4694,7 +4690,7 @@ end;
|
||||
|
||||
function TWDSPEngine.NormalizeFFTSize(FFTSize: Integer): Integer;
|
||||
begin
|
||||
// Thetis-style display FFT range.
|
||||
// Диапазон display FFT.
|
||||
if FFTSize < 4096 then FFTSize := 4096;
|
||||
Result := 4096;
|
||||
while Result < FFTSize do
|
||||
@@ -4705,7 +4701,7 @@ end;
|
||||
|
||||
function TWDSPEngine.CalcDisplayFFTSize: Integer;
|
||||
begin
|
||||
// FFT Size теперь снова управляет display-analyzer как в Thetis,
|
||||
// FFT Size управляет display-analyzer,
|
||||
// но не влияет на аудио DSP цепочку.
|
||||
Result := FFFTSize;
|
||||
end;
|
||||
@@ -4716,7 +4712,7 @@ var
|
||||
TargetFPS: Double;
|
||||
begin
|
||||
TargetFPS := EnsureRange(FDisplayFPS, 5, 100);
|
||||
// Thetis/piHPSDR-style timing:
|
||||
// Тайминг обновления дисплея:
|
||||
// целимся в пользовательский FPS и разрешаем большой overlap даже на крупных FFT.
|
||||
Overlap := Max(0, Ceil(AnalyzerFFT - (FSampleRate / TargetFPS)));
|
||||
EffectiveFPS := TargetFPS;
|
||||
@@ -4729,7 +4725,7 @@ begin
|
||||
|
||||
if ForWaterfall then
|
||||
begin
|
||||
// Peak hold на водопаде даёт грязную/залипающую картинку и не похож на Thetis.
|
||||
// Peak hold на водопаде даёт грязную и залипающую картинку.
|
||||
if AvgMode < 0 then AvgMode := 1;
|
||||
end
|
||||
else
|
||||
|
||||
Reference in New Issue
Block a user