mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +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:
+30
-16
@@ -108,6 +108,12 @@ const
|
||||
// TX mic ring buffer — должен быть степенью двойки
|
||||
TX_MIC_RING = 8192;
|
||||
|
||||
// TX/DUC sample rate. Фиксирован независимо от FSampleRate (RX).
|
||||
// Thetis/piHPSDR держат TX rate постоянным (192k для Hermes/Saturn DUC0),
|
||||
// меняя только RX rate. Это гарантирует, что DUC IQ всегда подаётся в том
|
||||
// темпе, который ожидает железо (DUC0RateHi/Lo в DUC Specific = 192).
|
||||
TX_SAMPLE_RATE = 192000;
|
||||
|
||||
type
|
||||
// Пакет в очереди между сетевым и DSP потоком
|
||||
TIQQueueItem = record
|
||||
@@ -145,6 +151,8 @@ type
|
||||
FAudioRate: Integer;
|
||||
FBufSize: Integer; // входной буфер @ FSampleRate
|
||||
FAudioBufSize: Integer; // выходной буфер @ FAudioRate (= FBufSize * FAudioRate / FSampleRate)
|
||||
FTXSampleRate: Integer; // фиксированный rate TX/DUC (TX_SAMPLE_RATE), не меняется при ChangeSampleRate
|
||||
FTXOutBufSize: Integer; // выходной буфер TXA в IQ парах @ FTXSampleRate (= FAudioBufSize * FTXSampleRate / FAudioRate)
|
||||
|
||||
// Interleaved double I/Q буферы для fexchange0
|
||||
FRXIn: array of Double;
|
||||
@@ -386,6 +394,7 @@ type
|
||||
|
||||
property Initialized: Boolean read FInitialized;
|
||||
property SampleRate: Integer read FSampleRate;
|
||||
property TXSampleRate: Integer read FTXSampleRate;
|
||||
property AudioBufSize: Integer read FAudioBufSize;
|
||||
property AGCTop: Double read FAGCTop;
|
||||
property AGCSlope: Integer read FAGCSlope;
|
||||
@@ -644,6 +653,9 @@ begin
|
||||
// piHPSDR: 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.
|
||||
FTXSampleRate := TX_SAMPLE_RATE;
|
||||
FTXOutBufSize := BufSize * FTXSampleRate div AudioRate; // = 4096 @ 48k audio, 192k tx
|
||||
FInitialized := False;
|
||||
FAnalyzerOpen := False;
|
||||
FMode := MODE_USB;
|
||||
@@ -672,10 +684,10 @@ begin
|
||||
FWfAvgMode := 3; // Log Recursive
|
||||
FWfAvgTimeMS := 120.0;
|
||||
|
||||
SetLength(FRXIn, FBufSize * 2); // in_size пар @ FSampleRate (4096*2)
|
||||
SetLength(FRXOut, FAudioBufSize * 2); // out_size пар @ FAudioRate (1024*2)
|
||||
SetLength(FTXIn, FAudioBufSize * 2);
|
||||
SetLength(FTXOut, FBufSize * 2);
|
||||
SetLength(FRXIn, FBufSize * 2); // in_size пар @ FSampleRate (4096*2)
|
||||
SetLength(FRXOut, FAudioBufSize * 2); // out_size пар @ FAudioRate (1024*2)
|
||||
SetLength(FTXIn, FAudioBufSize * 2); // in_size пар @ FAudioRate
|
||||
SetLength(FTXOut, FTXOutBufSize * 2); // out_size пар @ FTXSampleRate (фикс)
|
||||
SetLength(FRXAccI, FBufSize);
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
SetLength(FSpecBuf, FBufSize * 2);
|
||||
@@ -847,7 +859,7 @@ const KEEP_TIME_SEC = 0.10;
|
||||
begin
|
||||
AnalyzerFFT := NormalizeFFTSize(FTXFFTSize);
|
||||
FTXDispPos := 0;
|
||||
MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FSampleRate,
|
||||
MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FTXSampleRate,
|
||||
KEEP_TIME_SEC * AnalyzerFFT * EnsureRange(FDisplayFPS, 5, 100)));
|
||||
CalcAnalyzerTiming(AnalyzerFFT, Overlap, EffectiveFPS);
|
||||
DisplayPixels := EnsureRange(FLastSpectrumW, 64, SPECTRUM_PIXELS);
|
||||
@@ -868,7 +880,7 @@ begin
|
||||
SetDisplayNumAverage (TX_DISP_ID, 1, WfAvgCount);
|
||||
SetDisplayAvBackmult (TX_DISP_ID, 1, WfBackmult);
|
||||
SetDisplayNormOneHz (TX_DISP_ID, 1, 0);
|
||||
SetDisplaySampleRate (TX_DISP_ID, FSampleRate);
|
||||
SetDisplaySampleRate (TX_DISP_ID, FTXSampleRate);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyAnalyzerSettings;
|
||||
@@ -951,16 +963,16 @@ begin
|
||||
SetRXAPanelRun(RXA_CHAN, 1);
|
||||
SetChannelState(RXA_CHAN, 1, 0);
|
||||
|
||||
// TXA: mic @ FAudioRate → IQ @ FSampleRate (DUC rate)
|
||||
// out_size = FAudioBufSize * FSampleRate/FAudioRate = FBufSize (4096 @ 192kHz)
|
||||
// FTXOut уже имеет размер FBufSize*2 — подходит идеально
|
||||
// TXA: mic @ FAudioRate → IQ @ FTXSampleRate (фиксированный DUC rate, не зависит от RX).
|
||||
// out_size = FAudioBufSize * FTXSampleRate/FAudioRate = FTXOutBufSize (4096 @ 192k tx).
|
||||
// FTXOut уже имеет размер FTXOutBufSize*2.
|
||||
OpenChannel(
|
||||
TXA_CHAN,
|
||||
FAudioBufSize,
|
||||
FAudioBufSize,
|
||||
FAudioRate,
|
||||
FAudioRate,
|
||||
FSampleRate,
|
||||
FTXSampleRate,
|
||||
1,
|
||||
0,
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
@@ -969,7 +981,8 @@ begin
|
||||
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(FMode));
|
||||
SetTXAPanelRun(TXA_CHAN, 1);
|
||||
SetTXAPanelSelect(TXA_CHAN, 1);
|
||||
// PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I.
|
||||
SetTXAPanelSelect(TXA_CHAN, 2);
|
||||
|
||||
FInitialized := True;
|
||||
// Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ,
|
||||
@@ -1186,6 +1199,7 @@ begin
|
||||
FMode := Mode;
|
||||
if not FInitialized then Exit;
|
||||
SetRXAMode(RXA_CHAN, ModeToWDSP(Mode));
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(Mode));
|
||||
// НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI
|
||||
end;
|
||||
|
||||
@@ -1490,7 +1504,7 @@ end;
|
||||
|
||||
procedure TWDSPEngine.ProcessTXBlock;
|
||||
// Вызывается из TTXDSPThread — обрабатывает один блок FAudioBufSize mic сэмплов
|
||||
// через WDSP TXA, выдаёт FBufSize IQ пар @ FSampleRate в FOnTXIQ callback.
|
||||
// через WDSP TXA, выдаёт FTXOutBufSize IQ пар @ FTXSampleRate в FOnTXIQ callback.
|
||||
// Аналог tx_full_buffer() в piHPSDR/transmitter.c:
|
||||
// если mic ring buffer содержит достаточно данных — берём их,
|
||||
// иначе обрабатываем тишину (нули), как piHPSDR при mic_sample=0.0.
|
||||
@@ -1526,15 +1540,15 @@ begin
|
||||
// TX сигнал → отдельный TX-analyzer (TX_DISP_ID).
|
||||
// Подаём чанками по DISPLAY_BLOCK_SIZE — analyzer ожидает именно такой
|
||||
// размер блока (см. SetAnalyzer). Если подавать FTXOut целиком
|
||||
// (FBufSize пар), analyzer прочитает только первые DISPLAY_BLOCK_SIZE
|
||||
// (FTXOutBufSize пар), analyzer прочитает только первые DISPLAY_BLOCK_SIZE
|
||||
// и проигнорирует остальное → спектр будет пустой.
|
||||
if FAnalyzerOpen then
|
||||
for i := 0 to FBufSize - 1 do
|
||||
for i := 0 to FTXOutBufSize - 1 do
|
||||
FeedTXDisplaySample(FTXOut[i * 2], FTXOut[i * 2 + 1]);
|
||||
|
||||
// FTXOut содержит FBufSize IQ пар @ FSampleRate (interleaved double)
|
||||
// FTXOut содержит FTXOutBufSize IQ пар @ FTXSampleRate (interleaved double)
|
||||
if Assigned(FOnTXIQ) then
|
||||
FOnTXIQ(FTXOut, FBufSize);
|
||||
FOnTXIQ(FTXOut, FTXOutBufSize);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
||||
|
||||
Reference in New Issue
Block a user