mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
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:
+87
-16
@@ -217,6 +217,11 @@ type
|
||||
FSpecRefLevel: Double;
|
||||
FSpecRange: Double;
|
||||
FSpecGridStep: Double;
|
||||
// TX-specific grid (Thetis-style: при TX рисуем спектр в своих границах).
|
||||
// Применяются к FSpecView когда FTransmitting=True; иначе используются RX-значения.
|
||||
FTXSpecRefLevel: Double;
|
||||
FTXSpecRange: Double;
|
||||
FTXSpecGridStep: Double;
|
||||
// DSP data buffers (copy of last DSP output, for WebServer.PushSpectrum)
|
||||
FSpectrumBuf: array[0..1023] of Single;
|
||||
FWaterfallBuf: array[0..1023] of Single;
|
||||
@@ -559,6 +564,10 @@ type
|
||||
procedure ApplyWaterfallParams(WfDet, WfAvgMode: Integer;
|
||||
WfAvgTimeMS, WfHigh, WfLow, WfAGCOffset: Double);
|
||||
procedure ApplyGridParams(RefLevel, Range, GridStep: Double);
|
||||
// Пушит активные grid-параметры (RX или TX в зависимости от FTransmitting)
|
||||
// в FSpecView и сбрасывает кэш сетки. Вызывается при смене RX↔TX и при
|
||||
// правке любого из соответствующих полей (FSpec*, FTXSpec*).
|
||||
procedure ApplySpecViewGridFromState;
|
||||
procedure ApplyAudioDevice(DevIndex: Integer; const DevName: string);
|
||||
procedure ApplyAudioInputDevice(DevIndex: Integer; const DevName: string);
|
||||
// TX settings: применить FTXSettings к WDSP и переслать DUC Specific
|
||||
@@ -999,6 +1008,10 @@ begin
|
||||
FSpecRefLevel := -20.0;
|
||||
FSpecRange := 110.0;
|
||||
FSpecGridStep := 10.0;
|
||||
// TX grid defaults (синхронно с TSettingsManager.DefaultTX)
|
||||
FTXSpecRefLevel := 0.0;
|
||||
FTXSpecRange := 80.0;
|
||||
FTXSpecGridStep := 10.0;
|
||||
FSettingsForm := nil;
|
||||
FillChar(FDevMAC, SizeOf(FDevMAC), 0);
|
||||
|
||||
@@ -1108,6 +1121,8 @@ begin
|
||||
FSpecView.SpecRefLevel := FSpecRefLevel;
|
||||
FSpecView.SpecRange := FSpecRange;
|
||||
FSpecView.SpecGridStep := FSpecGridStep;
|
||||
// TX overlay: ширина окна = TX_SAMPLE_RATE из движка (фикс 192k)
|
||||
FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE;
|
||||
FSpecView.WfFrameInterval := FWaterfallFrameInterval;
|
||||
|
||||
FNetwork := THPSDRNetwork.Create;
|
||||
@@ -2487,6 +2502,12 @@ begin
|
||||
FSpecView.SpanHz := FSpanHz;
|
||||
FSpecView.Mode := FMode;
|
||||
FSpecView.FilterBW := FFilterBW;
|
||||
// TX overlay следует за активной TX-частотой (учитывает split). При CTUN
|
||||
// спектр TX будет отрисован на VFO, а не в центре дисплея.
|
||||
FSpecView.TXFreq := ActiveTXFreqHz;
|
||||
// Какой VFO привязан к TX (для split-полос фильтра): 1=B при FSplitTxB, иначе FActiveVfo.
|
||||
if FSplitTxB then FSpecView.TXVfoIndex := 1
|
||||
else FSpecView.TXVfoIndex := FActiveVfo;
|
||||
end;
|
||||
|
||||
|
||||
@@ -2855,34 +2876,38 @@ end;
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function TMainForm.BuildMicLineSelectByte: Byte;
|
||||
// HPSDR Protocol-2, DUC Specific byte 50:
|
||||
// bit 0 — mic boost (+20 dB)
|
||||
// bit 1 — Line in вместо mic in
|
||||
// bit 2 — Mic-jack PTT enable (tip)
|
||||
// bit 4 — Mic bias on (электретный капсюль)
|
||||
// Биты 3 (PTT tip-bias-ring) и 5..7 не используются.
|
||||
// openHPSDR Ethernet Protocol v4.3, DUC Specific byte 50:
|
||||
// bit 0 — Line in (1 = line-in вместо mic-in)
|
||||
// bit 1 — Mic Boost (+20 dB)
|
||||
// bit 2 — 0 = Orion mic PTT enabled, 1 = disabled (инверсия!)
|
||||
// bit 3 — PTT/bias polarity (не используем)
|
||||
// bit 4 — Orion mic bias (1 = enable +5V bias)
|
||||
// bit 5 — Saturn 3.5mm/XLR (не используем)
|
||||
begin
|
||||
Result := 0;
|
||||
if FTXSettings.MicBoost then Result := Result or $01;
|
||||
if FTXSettings.MicLineIn then Result := Result or $02;
|
||||
if FTXSettings.MicPTTEnabled then Result := Result or $04;
|
||||
if FTXSettings.MicBias then Result := Result or $10;
|
||||
if FTXSettings.MicLineIn then Result := Result or $01;
|
||||
if FTXSettings.MicBoost then Result := Result or $02;
|
||||
if not FTXSettings.MicPTTEnabled then Result := Result or $04;
|
||||
if FTXSettings.MicBias then Result := Result or $10;
|
||||
end;
|
||||
|
||||
procedure TMainForm.SendDUCSpecificFromSettings;
|
||||
var
|
||||
DUCPkt: TDUCSpecificPacket;
|
||||
RateKsps: Word;
|
||||
begin
|
||||
if not (FNetwork.Connected and FNetwork.Running) then Exit;
|
||||
FillChar(DUCPkt, SizeOf(DUCPkt), 0);
|
||||
// DUC0 rate в ksps берём из WDSP-движка (фиксированный TX rate, не RX).
|
||||
RateKsps := FDSPEngine.TXSampleRate div 1000;
|
||||
DUCPkt.NumDACs := 1;
|
||||
DUCPkt.SidetoneLevel := 50;
|
||||
DUCPkt.SidetoneFreqHi := Hi(600);
|
||||
DUCPkt.SidetoneFreqLo := Lo(600);
|
||||
DUCPkt.KeyerSpeed := 20;
|
||||
DUCPkt.KeyerWeight := 50;
|
||||
DUCPkt.DUC0RateHi := Hi(192);
|
||||
DUCPkt.DUC0RateLo := Lo(192);
|
||||
DUCPkt.DUC0RateHi := Hi(RateKsps);
|
||||
DUCPkt.DUC0RateLo := Lo(RateKsps);
|
||||
DUCPkt.DUC0Bits := 24;
|
||||
DUCPkt.MicLineSelect := BuildMicLineSelectByte;
|
||||
FNetwork.SendDUCSpecific(DUCPkt);
|
||||
@@ -3290,6 +3315,10 @@ begin
|
||||
CATApplySettings(G_Settings);
|
||||
// TX-настройки per-device — отдельная JSON-секция "tx"
|
||||
FSettings.LoadTX(FDevMAC, FTXSettings);
|
||||
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
|
||||
FTXSpecRange := FTXSettings.TXSpecRange;
|
||||
if FTXSettings.TXSpecGridStep > 0 then
|
||||
FTXSpecGridStep := FTXSettings.TXSpecGridStep;
|
||||
FVolume := G_Settings.Volume;
|
||||
FActiveVfo := G_Settings.ActiveVfo;
|
||||
// PA settings
|
||||
@@ -3431,6 +3460,7 @@ begin
|
||||
SendDUCSpecificFromSettings;
|
||||
// Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...).
|
||||
ApplyTXSettingsToDSP;
|
||||
ApplyModeFilter;
|
||||
|
||||
// Принудительно обновляем AGC линии — гарантированно после всех SetAGC вызовов
|
||||
if FWDSPReady then
|
||||
@@ -4096,8 +4126,10 @@ begin
|
||||
7: begin Lo := -Half; Hi := Half; end; // SAM
|
||||
else Lo := -Half; Hi := Half;
|
||||
end;
|
||||
if FWDSPReady then
|
||||
if FWDSPReady then begin
|
||||
FDSPEngine.SetFilter(Lo, Hi);
|
||||
FDSPEngine.SetTXFilter(Lo, Hi);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnModeClick(Sender: TObject);
|
||||
@@ -4111,6 +4143,7 @@ begin
|
||||
FDSPEngine.SetMode(N);
|
||||
UpdateFilterButtons; // обновляем подписи и дефолт фильтра под новый режим
|
||||
ApplyModeFilter;
|
||||
SyncSpecViewFreq; // FSpecView.Mode/FilterBW — иначе полоса рисуется под старый режим
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnFilterClick(Sender: TObject);
|
||||
@@ -4128,6 +4161,7 @@ begin
|
||||
end;
|
||||
for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FFilter);
|
||||
ApplyModeFilter;
|
||||
SyncSpecViewFreq;
|
||||
end;
|
||||
|
||||
procedure TMainForm.BtnVfoSwapClick(Sender: TObject);
|
||||
@@ -4223,6 +4257,13 @@ begin
|
||||
BtnMOX.Active := True;
|
||||
end else
|
||||
StyleButton(BtnMOX, False);
|
||||
// TX-overlay в SpectrumView: фиксируем актуальную TX-частоту/VFO-индекс и режим
|
||||
FSpecView.TXFreq := ActiveTXFreqHz;
|
||||
if FSplitTxB then FSpecView.TXVfoIndex := 1
|
||||
else FSpecView.TXVfoIndex := FActiveVfo;
|
||||
FSpecView.TXMode := FTransmitting;
|
||||
// Перерисовываем сетку спектра под активный тракт (Thetis-style).
|
||||
ApplySpecViewGridFromState;
|
||||
if FWDSPReady then
|
||||
FDSPEngine.SetTXRun(FTransmitting);
|
||||
if FNetwork.Connected and FNetwork.Running then
|
||||
@@ -4396,6 +4437,7 @@ begin
|
||||
FDSPEngine.SetMode(FMode);
|
||||
ApplyModeFilter;
|
||||
end;
|
||||
SyncSpecViewFreq;
|
||||
if Assigned(FVfoOverlay) and FVfoOverlay.Visible then
|
||||
begin
|
||||
FVfoOverlay.SetState(FMode, FFilterBW, FVfoA, FLastSMeter);
|
||||
@@ -4516,6 +4558,7 @@ begin
|
||||
if FWDSPReady then FDSPEngine.SetMode(FMode);
|
||||
UpdateFilterButtons;
|
||||
ApplyModeFilter;
|
||||
SyncSpecViewFreq;
|
||||
FBandCache[FCurrentBand].Mode := FMode;
|
||||
end;
|
||||
|
||||
@@ -4547,6 +4590,7 @@ begin
|
||||
end;
|
||||
for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FFilter);
|
||||
ApplyModeFilter;
|
||||
SyncSpecViewFreq;
|
||||
FBandCache[FCurrentBand].FilterBW := FFilterBW;
|
||||
end;
|
||||
Exit;
|
||||
@@ -4569,6 +4613,7 @@ begin
|
||||
FFilter := best;
|
||||
for j := 0 to FILT_COUNT-1 do StyleButton(BtnFilter[j], j = FFilter);
|
||||
ApplyModeFilter;
|
||||
SyncSpecViewFreq;
|
||||
FBandCache[FCurrentBand].FilterBW := FFilterBW;
|
||||
end;
|
||||
|
||||
@@ -4984,10 +5029,21 @@ procedure TMainForm.OnTXSettingsChange(const T: TTXSettings);
|
||||
// перешиваем WDSP TX-цепь и (если поменялись mic-биты) перепосылаем DUC Specific.
|
||||
var
|
||||
OldMicByte, NewMicByte: Byte;
|
||||
GridChanged: Boolean;
|
||||
begin
|
||||
OldMicByte := BuildMicLineSelectByte;
|
||||
GridChanged := (FTXSpecRefLevel <> T.TXSpecRefLevel)
|
||||
or (FTXSpecRange <> T.TXSpecRange)
|
||||
or (FTXSpecGridStep <> T.TXSpecGridStep);
|
||||
FTXSettings := T;
|
||||
NewMicByte := BuildMicLineSelectByte;
|
||||
// TX-grid в момент правки — обновляем рабочие поля и (если на передаче) пушим в FSpecView
|
||||
FTXSpecRefLevel := FTXSettings.TXSpecRefLevel;
|
||||
FTXSpecRange := FTXSettings.TXSpecRange;
|
||||
if FTXSettings.TXSpecGridStep > 0 then
|
||||
FTXSpecGridStep := FTXSettings.TXSpecGridStep;
|
||||
if GridChanged and FTransmitting then
|
||||
ApplySpecViewGridFromState;
|
||||
ApplyTXSettingsToDSP;
|
||||
if OldMicByte <> NewMicByte then
|
||||
SendDUCSpecificFromSettings;
|
||||
@@ -5164,10 +5220,25 @@ begin
|
||||
FSpecRefLevel := RefLevel;
|
||||
FSpecRange := Range;
|
||||
if GridStep > 0 then FSpecGridStep := GridStep;
|
||||
FSpecView.SpecRefLevel := FSpecRefLevel;
|
||||
FSpecView.SpecRange := FSpecRange;
|
||||
FSpecView.SpecGridStep := FSpecGridStep;
|
||||
// На передаче FSpecView показывает TX-сетку — RX-правка не должна её перетирать.
|
||||
if not FTransmitting then ApplySpecViewGridFromState;
|
||||
end;
|
||||
|
||||
procedure TMainForm.ApplySpecViewGridFromState;
|
||||
begin
|
||||
if FTransmitting then
|
||||
begin
|
||||
FSpecView.SpecRefLevel := FTXSpecRefLevel;
|
||||
FSpecView.SpecRange := FTXSpecRange;
|
||||
FSpecView.SpecGridStep := FTXSpecGridStep;
|
||||
end else
|
||||
begin
|
||||
FSpecView.SpecRefLevel := FSpecRefLevel;
|
||||
FSpecView.SpecRange := FSpecRange;
|
||||
FSpecView.SpecGridStep := FSpecGridStep;
|
||||
end;
|
||||
InvalidateGridCache;
|
||||
FSpectrumDirty := True;
|
||||
end;
|
||||
|
||||
procedure TMainForm.ApplyAudioDevice(DevIndex: Integer; const DevName: string);
|
||||
|
||||
Reference in New Issue
Block a user