TX: фиксированный sample rate, per-device grid, CTUN-aware overlay, alpha-blend filter bands

Декаплинг TX от RX sample rate (как в Thetis):
- WDSPEngine.pas: константа TX_SAMPLE_RATE=192000, поля FTXSampleRate/FTXOutBufSize.
  TXA OpenChannel теперь использует FTXSampleRate как out_rate, TX-анализатор
  через SetDisplaySampleRate(TX_DISP_ID, FTXSampleRate). ProcessTXBlock работает
  с FTXOutBufSize вместо FBufSize. Public TXSampleRate для MainForm.
- MainForm.SendDUCSpecificFromSettings: DUC0Rate теперь берётся из
  FDSPEngine.TXSampleRate, а не из RX-настроек — TX всегда 192k независимо от
  выбранного RX-rate (48/96/192/384k).

DUC Specific cache + keepalive resend (HPSDRNetwork.pas):
- FCachedDUCSpec/FCachedDUCValid параллельно DDC-кэшу.
- KeepAlive thread в первые 5s после Run=1 переотправляет и DDC, и DUC
  Specific — устраняет гонку, когда DUC настройки терялись железом.
- Disconnect сбрасывает оба valid-флага.

Per-device TX grid settings (Thetis-style):
- Settings.pas: TXSpecRefLevel/TXSpecRange/TXSpecGridStep в TTXSettings
  (defaults 0.0/80.0/10.0). LoadTX/SaveTX — ключи tx_spec_ref_level/range/grid_step.
- SettingsForm.pas: новая группа \"TX Grid\" во вкладке Transmit
  (RefLevel edit, Range edit, GridStep combo). CollectTXFromUI читает значения,
  LoadTXSettings заполняет UI.
- MainForm.pas: поля FTXSpecRefLevel/Range/GridStep, ApplySpecViewGridFromState
  пушит в FSpecView либо TX, либо RX параметры в зависимости от FTransmitting.
  ApplyGridParams (RX-путь) уважает FTransmitting и не перебивает TX-grid.
  OnTXSettingsChange при активном TX моментально применяет новый grid.

TX-aware spectrum/waterfall с CTUN-выравниванием (SpectrumView.pas):
- Поля FTXMode/FTXFreq/FTXSpanHz/FTXVfoIndex. TXSpanHz=192000 при создании.
- DrawSpectrum/DrawWaterfall в FTXMode мапят данные TX-анализатора (192k вокруг
  FTXFreq) в RX-координаты FCenterFreq/FSpanHz. Формула:
    freq = FCenterFreq - FSpanHz/2 + x*FSpanHz/W
    idx  = (freq-FTXFreq + TXSpanHz/2)/TXSpanHz * (SrcCount-1)
  Вне диапазона — спектр уходит в -200 dB; водопад копирует пиксель из
  предыдущей строки (FWfPixels[X+W]) — вместо чёрной полосы продолжает
  скроллиться старая RX-картинка.
- Эффект: при CTUN ON TX-сигнал рисуется на полосе фильтра (на VFO), а не в
  центре. При RX rate ≠ 192k частотная сетка совпадает между RX и TX
  автоматически (ruler всегда в RX-координатах).
- MainForm.SyncSpecViewFreq/ApplyMOX обновляют TXFreq/TXVfoIndex/TXMode при
  каждом PTT и смене VFO; учитывается split (FSplitTxB → TXVfoIndex=1).

Alpha-blended filter bands (визуальный feedback TX/RX):
- SpectrumView: PixelFormat:=pf32bit, процедура BlendBand через ScanLine[Y]
  внутри BeginUpdate/EndUpdate. Альфа-смешение по формуле
  (a*src + (255-a)*dst) / 255 для каждого канала BGR.
- В FTXMode полоса фильтра становится полупрозрачно красной. Если split
  (FTXVfoIndex<>FActiveVfo): на RX-частоте полоса зелёная, на TX-частоте —
  красная (CalcFilterBandX вычисляет границы для произвольной VFO-freq).
- При FTXMode=False — старое поведение (сплошная заливка SpecFilter).

Build: lazbuild --ws=qt6 ewsdr.lpi — clean, 0 errors.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 14:17:49 +03:00
co-authored by Claude Opus 4.7
parent ce97ee486c
commit 96d7e0c759
6 changed files with 400 additions and 68 deletions
+60
View File
@@ -167,6 +167,10 @@ type
FCmbTXWfDet: TComboBox;
FCmbTXWfAvg: TComboBox;
FEdTXWfAvgTime: TFloatSpinEdit;
// TX Grid (отдельные от RX grid в Display)
FEdTXRefLevel: TEdit;
FEdTXRange: TEdit;
FCmbTXGridStep: TComboBox;
// ---- CAT tab controls ----
FCATSerialEn: array[0..3] of TCheckBox;
@@ -1084,6 +1088,37 @@ begin
FCmbTXWfAvg.ItemIndex := 3;
MakeLbl(Grp, 'Avg time (ms)', PAD + 430, R1 + 5 * STEP + 5, 100);
FEdTXWfAvgTime := MkFloat(Grp, PAD + 540, R1 + 5 * STEP, 120.0, 1.0, 5000.0, 1.0);
// ---- TX Grid (RefLevel / Range / GridStep — отдельные от RX) ----
Inc(Y, 290 + 12);
Grp := MakeGroupPanel(FPageTransmit, 'TX Grid', MARGIN, Y, 700, 170);
MakeLbl(Grp, 'Reference level', PAD, R1 + 5, LW);
FEdTXRefLevel := TEdit.Create(Self);
FEdTXRefLevel.Parent := Grp;
FEdTXRefLevel.SetBounds(CX, R1, 96, BTN_H);
FEdTXRefLevel.Color := CLR_INPUT;
FEdTXRefLevel.Font.Color := CLR_INPUT_TEXT;
FEdTXRefLevel.Font.Name := UI_FONT;
FEdTXRefLevel.Font.Size := 9;
FEdTXRefLevel.Text := '0';
FEdTXRefLevel.OnExit := OnTXAnyChange;
MakeLbl(Grp, 'Range (dB)', PAD, R1 + STEP + 5, LW);
FEdTXRange := TEdit.Create(Self);
FEdTXRange.Parent := Grp;
FEdTXRange.SetBounds(CX, R1 + STEP, 96, BTN_H);
FEdTXRange.Color := CLR_INPUT;
FEdTXRange.Font.Color := CLR_INPUT_TEXT;
FEdTXRange.Font.Name := UI_FONT;
FEdTXRange.Font.Size := 9;
FEdTXRange.Text := '80';
FEdTXRange.OnExit := OnTXAnyChange;
MakeLbl(Grp, 'Grid step', PAD, R1 + 2 * STEP + 5, LW);
FCmbTXGridStep := MkCmb(Grp, CX, R1 + 2 * STEP, 130);
for i := 0 to High(GRID_STEP_NAMES) do FCmbTXGridStep.Items.Add(GRID_STEP_NAMES[i]);
FCmbTXGridStep.ItemIndex := 1;
end;
procedure TSettingsForm.CollectTXFromUI;
@@ -1139,6 +1174,19 @@ begin
if (FCmbTXWfAvg <> nil) and (FCmbTXWfAvg.ItemIndex >= 0) then
FTX.TXWfAvgMode := WF_AVGMODE_VALUES[FCmbTXWfAvg.ItemIndex];
if FEdTXWfAvgTime <> nil then FTX.TXWfAvgTimeMS := FEdTXWfAvgTime.Value;
if FEdTXRefLevel <> nil then
begin
Val(FEdTXRefLevel.Text, FTX.TXSpecRefLevel, i);
if i <> 0 then FTX.TXSpecRefLevel := 0.0;
end;
if FEdTXRange <> nil then
begin
Val(FEdTXRange.Text, FTX.TXSpecRange, i);
if i <> 0 then FTX.TXSpecRange := 80.0;
end;
if (FCmbTXGridStep <> nil) and (FCmbTXGridStep.ItemIndex >= 0)
and (FCmbTXGridStep.ItemIndex <= High(GRID_STEPS)) then
FTX.TXSpecGridStep := GRID_STEPS[FCmbTXGridStep.ItemIndex];
end;
procedure TSettingsForm.FireTXChange;
@@ -1241,6 +1289,18 @@ begin
end;
end;
if FEdTXWfAvgTime <> nil then FEdTXWfAvgTime.Value := T.TXWfAvgTimeMS;
if FEdTXRefLevel <> nil then FEdTXRefLevel.Text := FormatFloat('0.#', T.TXSpecRefLevel);
if FEdTXRange <> nil then FEdTXRange.Text := FormatFloat('0.#', T.TXSpecRange);
if FCmbTXGridStep <> nil then
begin
FCmbTXGridStep.ItemIndex := 1; // дефолт 10 dB
for i := 0 to High(GRID_STEPS) do
if Abs(GRID_STEPS[i] - T.TXSpecGridStep) < 0.5 then
begin
FCmbTXGridStep.ItemIndex := i;
Break;
end;
end;
finally
FLoading := False;
end;