From c2ca52511c3966a9aed5b79e434d9a789e35f1e0 Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Wed, 27 May 2026 11:29:39 +0300 Subject: [PATCH] rx2 support work --- HPSDRNetwork.pas | 79 +++- MainForm.pas | 962 +++++++++++++++++++++++++++++++++++++++++- SampleRateOverlay.pas | 9 +- WDSPEngine.pas | 498 ++++++++++++---------- 4 files changed, 1290 insertions(+), 258 deletions(-) diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas index 0bbd229..7129977 100644 --- a/HPSDRNetwork.pas +++ b/HPSDRNetwork.pas @@ -151,6 +151,8 @@ type // Текущее состояние для построения HP пакетов FCurrentRXFreq: Double; FCurrentTXFreq: Double; + FDDCFreq: array[0..MAX_DDCS - 1] of Double; + FDDCEnabled: array[0..MAX_DDCS - 1] of Boolean; FCurrentDrive: Byte; FIsTransmitting: Boolean; FPTTActive: Boolean; // фактический PTT в HP-пакете (отдельно от Alex T/R) @@ -216,12 +218,18 @@ type procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0; DitherEnabled: Boolean = True; RandomEnabled: Boolean = True); + procedure ConfigureDDCReceivers(ReceiverCount: Byte; SampleRate: Word; + RX1ADCSource: Byte = 0; RX2ADCSource: Byte = 0; + DitherEnabled: Boolean = True; + RandomEnabled: Boolean = True; + RX2SampleRate: Word = 0); procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); procedure SendHighPriority(const Pkt: THighPriorityPacket); procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; DriveLevel: Byte = 100); procedure UpdateState(RXFreqHz, TXFreqHz: Double; DriveLevel: Byte; Transmitting, PAEnabled, AlexEnabled: Boolean); + procedure SetDDCFrequency(DDCIndex: Integer; FreqHz: Double); procedure SetStepAtten(dB: Byte); procedure SetAlexConfig(const A: TAlexSettings); // Включение/настройка XVTR-режима. Все параметры применяются мгновенно @@ -608,6 +616,8 @@ begin FWBSeqValid := False; FCurrentRXFreq := 7100000; FCurrentTXFreq := 7100000; + FillChar(FDDCEnabled, SizeOf(FDDCEnabled), 0); + FDDCFreq[0] := FCurrentRXFreq; FCurrentDrive := 0; FIsTransmitting := False; FPAEnabled := True; @@ -1376,15 +1386,29 @@ end; procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte; DitherEnabled: Boolean; RandomEnabled: Boolean); +begin + ConfigureDDCReceivers(NumDDCs, SampleRate, ADCSource, ADCSource, + DitherEnabled, RandomEnabled); +end; + +procedure THPSDRNetwork.ConfigureDDCReceivers(ReceiverCount: Byte; SampleRate: Word; + RX1ADCSource: Byte; RX2ADCSource: Byte; + DitherEnabled: Boolean; RandomEnabled: Boolean; + RX2SampleRate: Word); var Pkt: TDDCSpecificPacket; i, ddc: Integer; DDCBase: Integer; + ADCSource: Byte; + DDCRate: Word; begin FillChar(Pkt, SizeOf(Pkt), 0); - // ANAN-7000/8000/Saturn имеют 2 ADC - if FDevice.BoardType in [4, 5, 10] then // ORION=4, ORION2=5, SATURN=10 + if ReceiverCount < 1 then ReceiverCount := 1; + if ReceiverCount > 2 then ReceiverCount := 2; + + // ANAN-100D/200D/7000/8000/Saturn имеют второй ADC/RX front-end. + if FDevice.BoardType in [3, 4, 5, 10] then Pkt.NumADCs := 2 else Pkt.NumADCs := 1; @@ -1402,9 +1426,17 @@ begin else DDCBase := 0; - for i := 0 to NumDDCs - 1 do + FillChar(FDDCEnabled, SizeOf(FDDCEnabled), 0); + for i := 0 to ReceiverCount - 1 do begin ddc := DDCBase + i; + ADCSource := RX1ADCSource; + if i = 1 then ADCSource := RX2ADCSource; + if ADCSource >= Pkt.NumADCs then ADCSource := 0; + DDCRate := SampleRate; + if (i = 1) and (RX2SampleRate > 0) then DDCRate := RX2SampleRate; + + FDDCEnabled[ddc] := True; // Enable bit для DDC ddc Pkt.DDCEnable[ddc div 8] := Pkt.DDCEnable[ddc div 8] or Byte(1 shl (ddc mod 8)); // Config: 6 байт на DDC начиная с offset 17 в пакете → в массиве DDCConfig @@ -1412,8 +1444,8 @@ begin // DDCConfig[ddc*6 + 1..2] = sample rate / 1000 (ksps) // DDCConfig[ddc*6 + 5] = bits per sample Pkt.DDCConfig[ddc * 6] := ADCSource; - Pkt.DDCConfig[ddc * 6 + 1] := (SampleRate shr 8) and $FF; - Pkt.DDCConfig[ddc * 6 + 2] := SampleRate and $FF; + Pkt.DDCConfig[ddc * 6 + 1] := (DDCRate shr 8) and $FF; + Pkt.DDCConfig[ddc * 6 + 2] := DDCRate and $FF; Pkt.DDCConfig[ddc * 6 + 3] := 0; Pkt.DDCConfig[ddc * 6 + 4] := 0; Pkt.DDCConfig[ddc * 6 + 5] := 24; // 24 bits per sample @@ -1451,6 +1483,8 @@ procedure THPSDRNetwork.UpdateState(RXFreqHz, TXFreqHz: Double; Transmitting, PAEnabled, AlexEnabled: Boolean); begin FCurrentRXFreq := RXFreqHz; + FDDCFreq[0] := RXFreqHz; + FDDCFreq[2] := RXFreqHz; FCurrentTXFreq := TXFreqHz; FCurrentDrive := DriveLevel; FIsTransmitting := Transmitting; @@ -1458,6 +1492,15 @@ begin FAlexEnabled := AlexEnabled; end; +procedure THPSDRNetwork.SetDDCFrequency(DDCIndex: Integer; FreqHz: Double); +begin + if (DDCIndex < Low(FDDCFreq)) or (DDCIndex > High(FDDCFreq)) then Exit; + FDDCFreq[DDCIndex] := FreqHz; + if (DDCIndex = 0) or (DDCIndex = 2) then + FCurrentRXFreq := FreqHz; + if FConnected then SendFullHP; +end; + procedure THPSDRNetwork.SetStepAtten(dB: Byte); begin if dB > 31 then dB := 31; @@ -1473,6 +1516,9 @@ var Alex1: LongWord; IsOrion2: Boolean; DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA + DDCIdx: Integer; + DDCF: Double; + RX2Freq: Double; begin if not FConnected then Exit; @@ -1504,12 +1550,19 @@ begin else DDCBase := 0; - // DDC RX frequency (bytes 9 + DDC*4) - Ph := FreqToPhaseWord(FCurrentRXFreq); - Buf[9 + DDCBase*4] := (Ph shr 24) and $FF; - Buf[10 + DDCBase*4] := (Ph shr 16) and $FF; - Buf[11 + DDCBase*4] := (Ph shr 8) and $FF; - Buf[12 + DDCBase*4] := Ph and $FF; + // DDC RX frequencies (bytes 9 + DDC*4) + if FDDCFreq[DDCBase] <= 0 then FDDCFreq[DDCBase] := FCurrentRXFreq; + for DDCIdx := 0 to MAX_DDCS - 1 do + if FDDCEnabled[DDCIdx] or (DDCIdx = DDCBase) then + begin + DDCF := FDDCFreq[DDCIdx]; + if DDCF <= 0 then DDCF := FCurrentRXFreq; + Ph := FreqToPhaseWord(DDCF); + Buf[9 + DDCIdx*4] := (Ph shr 24) and $FF; + Buf[10 + DDCIdx*4] := (Ph shr 16) and $FF; + Buf[11 + DDCIdx*4] := (Ph shr 8) and $FF; + Buf[12 + DDCIdx*4] := Ph and $FF; + end; // DUC TX frequency (bytes 329-332) Ph := FreqToPhaseWord(FCurrentTXFreq); @@ -1537,7 +1590,9 @@ begin // ALEX1 filter bits (bytes 1428-1431) — только Orion2/Saturn if IsOrion2 then begin - Alex1 := CalcAlex1(FCurrentRXFreq, FCurrentTXFreq, + RX2Freq := FDDCFreq[DDCBase + 1]; + if RX2Freq <= 0 then RX2Freq := FCurrentRXFreq; + Alex1 := CalcAlex1(RX2Freq, FCurrentTXFreq, FIsTransmitting, FAlexConfig, FXvtrDisablePA); Buf[1428] := (Alex1 shr 24) and $FF; diff --git a/MainForm.pas b/MainForm.pas index 348b28e..1d94777 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -149,11 +149,14 @@ type FPendingIP: string; // IP устройства для подключения FVfoOverlay: TVfoOverlay; // накладка SmartSDR-стиль на спектре FSampleRateOverlay: TSampleRateOverlay; // оверлей span/hide слева вверху спектра + FVfoOverlayRX2: TVfoOverlay; + FSampleRateOverlayRX2: TSampleRateOverlay; FPanelHidden: Boolean; // True = левая панель скрыта FPendingBoardType: Integer; // BoardType устройства для подключения // ---- DSP + Audio ---- FDSPEngine: TWDSPEngine; + FDSPEngineRX2: TWDSPEngine; FAudioOut: TAudioOutput; FAudioIn: TAudioInput; FAudioOutDevName: string; // текущее имя выходного устройства @@ -211,6 +214,21 @@ type FRXStartTime: QWord; // GetTickCount64 момента нажатия START FRXLastPktTime: QWord; // GetTickCount64 последнего принятого пакета FActiveDDC: Integer; // DDC index для текущей платы (0 или 2) + FRX2Enabled: Boolean; + FRX2Available: Boolean; + FRX2DDC: Integer; + FRX2SampleRate: Integer; + FRX2SpanHz: Double; + FRX2Vfo: Double; + FRX2CenterFreq: Double; + FRX2Mode: Integer; + FRX2FilterBW: Integer; + FRX2AGCMode: Integer; + FRX2LastSMeter: Double; + FRX2SpectrumDirty: Boolean; + FRX2WaterfallDirty: Boolean; + FRX2Floating: Boolean; + FRX2Form: TForm; FLastDDCSeq: LongWord; // последний seq (пишется из сетевого потока) FLastDDCIndex: Integer; // последний DDC index FDDCLastSeq: array[0..6] of LongWord; @@ -229,6 +247,7 @@ type // ---- Spectrum / Waterfall view ---- FSpecView: TSpectrumView; + FSpecViewRX2: TSpectrumView; FWidebandView: TWidebandView; FUseOpenGLSpectrum: Boolean; FSpectrumWidth: Integer; // актуальная ширина для FDSPEngine и resize-детектора @@ -273,6 +292,7 @@ type // Waterfall frame timing FWaterfallFrameInterval: Integer; FWaterfallFrameCounter: Integer; + FRX2WaterfallFrameCounter: Integer; FAgcLineCounter: Integer; // ---- Splitter ---- FSplitterDrag: Boolean; @@ -360,6 +380,8 @@ type BtnFilter: array[0..FILT_COUNT-1] of TFlatButton; BtnCTun: TFlatButton; BtnDUP: TFlatButton; + PanelRXSwitch: TPanel; + BtnRX2: TFlatButton; PanelFMSQ: TPanel; BtnFMSQ: TFlatButton; TrkFMSQ: TFlatSlider; @@ -399,8 +421,11 @@ type PbWideband: TControl; PbWidebandRuler: TPaintBox; PbRuler: TPaintBox; // полоса частотных меток между спектром и водопадом + PbSpectrumRX2: TControl; + PbRulerRX2: TPaintBox; PanelSplitter: TPanel; // перетаскиваемый разделитель спектр/водопад PbWaterfall: TControl; + PbWaterfallRX2: TControl; // ---- Status bar ---- StatusPanel: TMainStatusBar; @@ -419,7 +444,10 @@ type procedure PositionSampleRateOverlay; procedure OnSampleRateOverlayInvalidate(Sender: TObject); procedure OnSampleRateSelect(SampleRate: Integer); + procedure OnRX2SampleRateSelect(SampleRate: Integer); procedure OnSampleRateHidePanel; + procedure OnRX2SampleRateOverlayAction; + procedure RX2FloatClose(Sender: TObject; var CloseAction: TCloseAction); procedure UpdateVfoDisplay; function FormatFreq(Hz: Double): string; procedure LayoutTopVfoBlock; @@ -431,6 +459,7 @@ type procedure SyncSpecViewFreq; procedure RecreateDSPEngine(ASampleRate: Integer); function EnsureWDSPWisdom: Boolean; + function CurrentBoardSupportsRX2: Boolean; // Network callbacks procedure OnDeviceFound(const Dev: THPSDRDevice); @@ -446,6 +475,8 @@ type procedure OnAudioReady(const Left, Right: array of Single; Count: Integer); procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer); procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer); + procedure OnRX2SpectrumReady(const Pixels: array of Single; Count: Integer); + procedure OnRX2WaterfallReady(const Pixels: array of Single; Count: Integer); // Public UI update (called from sync objects) procedure DoAddDevice(const Dev: THPSDRDevice; const Entry: string); @@ -461,6 +492,7 @@ type procedure BtnStartStopClick(Sender: TObject); procedure FreqDispAChanged(Sender: TObject; NewFreq: Int64); procedure ApplyVfoA(NewFreq: Int64); + procedure ApplyRX2Vfo(NewFreq: Int64); procedure FreqDispAClick(Sender: TObject); procedure FreqDispBClick(Sender: TObject); procedure BtnTopVfoASelectClick(Sender: TObject); @@ -518,6 +550,8 @@ type Shift: TShiftState; X, Y: Integer); procedure DoSpectrumClick(PixelX: Integer; PanelWidth: Integer); procedure DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer); + procedure DoRX2SpectrumClick(PixelX: Integer; PanelWidth: Integer); + procedure DoRX2SpectrumDrag(PixelX: Integer; PanelWidth: Integer); procedure DoWidebandClick(PixelX: Integer; PanelWidth: Integer); procedure BtnVfoSwapClick(Sender: TObject); procedure BtnVfoACopyBClick(Sender: TObject); @@ -643,11 +677,17 @@ type // ------------------------------------------------------------------------- procedure BtnSettingsClick(Sender: TObject); procedure PositionVfoOverlay; + procedure PositionRX2VfoOverlay; procedure OnModeFilterSelect(Mode: Integer; FilterBW: Integer); procedure OnVfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean); procedure OnVfoOverlayAGCChange(AGCMode: Integer); procedure OnVfoOverlayInvalidate(Sender: TObject); + procedure OnRX2ModeFilterSelect(Mode: Integer; FilterBW: Integer); + procedure OnRX2VfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean); + procedure OnRX2VfoOverlayAGCChange(AGCMode: Integer); + procedure OnRX2VfoOverlayInvalidate(Sender: TObject); procedure PbSpectrumDblClick(Sender: TObject); + procedure BtnRX2Click(Sender: TObject); procedure TrkDriveChange(Sender: TObject); procedure TrkVolumeChange(Sender: TObject); function CalcDriveByte: Byte; @@ -1031,11 +1071,16 @@ begin FSpanHz := FSampleRate; if Assigned(FSampleRateOverlay) then FSampleRateOverlay.SetCurrentRate(FSampleRate); + if Assigned(FSampleRateOverlayRX2) then + FSampleRateOverlayRX2.SetCurrentRate(FRX2SampleRate); // --- VFO A: центрируем на новой частоте, сбрасываем shift --- FCenterFreq := B.VfoA; // DDC = центр диапазона FVfoB := B.VfoB; - FreqDispB.Frequency := Round(FVfoB); + if FRX2Enabled then + FreqDispB.Frequency := Round(FRX2Vfo) + else + FreqDispB.Frequency := Round(FVfoB); // Пересчитываем drive byte для нового диапазона перед отправкой в сеть FDriveLevel := CalcDriveByte; @@ -1141,6 +1186,8 @@ begin begin FSampleRate := StartupRate; FSpanHz := StartupRate; + FRX2SampleRate := StartupRate; + FRX2SpanHz := StartupRate; end; end; // Восстанавливаем размер/позицию окна при старте (до подключения устройства) @@ -1209,6 +1256,21 @@ begin FSMeterAvg := -130; FRXPacketCount := 0; FActiveDDC := 0; + FRX2Enabled := False; + FRX2Available := False; + FRX2DDC := 1; + FRX2SampleRate := FSampleRate; + FRX2SpanHz := FSpanHz; + FRX2Vfo := FVfoB; + FRX2CenterFreq := FVfoB; + FRX2Mode := FMode; + FRX2FilterBW := FFilterBW; + FRX2AGCMode := FAGCMode; + FRX2LastSMeter := -130; + FRX2SpectrumDirty := False; + FRX2WaterfallDirty := False; + FRX2Floating := False; + FRX2Form := nil; FLastDDCSeq := 0; FLastDDCIndex := 0; FillChar(FDDCLastSeq, SizeOf(FDDCLastSeq), 0); @@ -1231,6 +1293,12 @@ begin FVfoOverlay.OnAGCChange := OnVfoOverlayAGCChange; FVfoOverlay.OnInvalidate := OnVfoOverlayInvalidate; FVfoOverlay.Visible := False; + FVfoOverlayRX2 := TVfoOverlay.Create(Self); + FVfoOverlayRX2.OnSelect := OnRX2ModeFilterSelect; + FVfoOverlayRX2.OnDSPChange := OnRX2VfoOverlayDSPChange; + FVfoOverlayRX2.OnAGCChange := OnRX2VfoOverlayAGCChange; + FVfoOverlayRX2.OnInvalidate := OnRX2VfoOverlayInvalidate; + FVfoOverlayRX2.Visible := True; FPanelHidden := False; FShowSpectrum := True; FShowWaterfall := True; @@ -1264,6 +1332,10 @@ begin FSpecView := TSpectrumViewOpenGL.Create else FSpecView := TSpectrumView.Create; + if FUseOpenGLSpectrum then + FSpecViewRX2 := TSpectrumViewOpenGL.Create + else + FSpecViewRX2 := TSpectrumView.Create; FSpecView.VfoOverlay := FVfoOverlay; FSpecView.VfoA := FVfoA; FSpecView.VfoB := FVfoB; @@ -1285,6 +1357,24 @@ begin FSpecView.TXSpanHz := WDSPEngine.TX_SAMPLE_RATE; FSpecView.WfFrameInterval := FWaterfallFrameInterval; FSpecView.PAMaxPower := FPAMaxPower; + FSpecViewRX2.VfoOverlay := FVfoOverlayRX2; + FSpecViewRX2.VfoA := FRX2Vfo; + FSpecViewRX2.VfoB := FRX2Vfo; + FSpecViewRX2.ActiveVfo := 0; + FSpecViewRX2.CenterFreq := FRX2CenterFreq; + FSpecViewRX2.SpanHz := FRX2SpanHz; + FSpecViewRX2.Mode := FRX2Mode; + FSpecViewRX2.FilterBW := FRX2FilterBW; + FSpecViewRX2.AGCTop := FAGCTop; + FSpecViewRX2.WfAGCEnabled := FWfAGCEnabled; + FSpecViewRX2.WfNFEnabled := FWfNFEnabled; + FSpecViewRX2.WfManualHigh := FWfManualHigh; + FSpecViewRX2.WfManualLow := FWfManualLow; + FSpecViewRX2.WfAGCOffset := FWfAGCOffset; + FSpecViewRX2.SpecRefLevel := FSpecRefLevel; + FSpecViewRX2.SpecRange := FSpecRange; + FSpecViewRX2.SpecGridStep := FSpecGridStep; + FSpecViewRX2.WfFrameInterval := FWaterfallFrameInterval; FWidebandView := TWidebandView.Create; FNetwork := THPSDRNetwork.Create; @@ -1319,6 +1409,9 @@ begin // Sound-card mic путь: WDSP TX-thread дёргает этот колбэк перед каждым // тиком, если выбран источник SoundCard. Для Radio колбэк не вызывается. FDSPEngine.OnPullMicSamples := PullSoundCardMic; + FDSPEngineRX2 := TWDSPEngine.Create(FRX2SampleRate, 48000, 512, 2, -1, 2, -1, False); + FDSPEngineRX2.OnSpectrum := OnRX2SpectrumReady; + FDSPEngineRX2.OnWaterfall := OnRX2WaterfallReady; // Audio output/input — создаём объекты сейчас, открываем после показа формы // (Pa_Initialize на Linux пишет в stderr до перехвата сигналов FPC) @@ -1366,7 +1459,13 @@ begin FAudioIn.Free; FDSPEngine.Close; FDSPEngine.Free; + if Assigned(FDSPEngineRX2) then + begin + FDSPEngineRX2.Close; + FDSPEngineRX2.Free; + end; FreeAndNil(FSpecView); + FreeAndNil(FSpecViewRX2); FreeAndNil(FWidebandView); // Сохраняем настройки при закрытии @@ -1669,6 +1768,20 @@ begin Inc(Y, 76); + // RX switch + PanelRXSwitch := TPanel.Create(Self); + PanelRXSwitch.Parent := PanelLeft; + PanelRXSwitch.SetBounds(0, Y, LEFT_W, 45); + PanelRXSwitch.BevelOuter := bvNone; + MakeLbl(PanelRXSwitch, 'RX', 4, 2); + BtnRX2 := MakeBtn(PanelRXSwitch, 'RX2', + LeftPanelButtonLeft(LEFT_W, 4, 0), 16, + LeftPanelButtonWidth(LEFT_W, 4, 0), BTN_SM, BtnRX2Click); + BtnRX2.Enabled := False; + StyleButton(BtnRX2, False); + + Inc(Y, 49); + // Band selector. Высота резервирует 2 дополнительных ряда под XVTR-кнопки // (создаются динамически в RebuildXvtrButtons). В каждом ряду до 6 кнопок, // итого до 12 XVTR-кнопок (наш CFG_XVTR_COUNT = 8 умещается с запасом). @@ -1925,6 +2038,13 @@ begin FSampleRateOverlay.OnInvalidate := OnSampleRateOverlayInvalidate; FSampleRateOverlay.SetCurrentRate(FSampleRate); FSpecView.SampleRateOverlay := FSampleRateOverlay; + FSampleRateOverlayRX2 := TSampleRateOverlay.Create(Self); + FSampleRateOverlayRX2.OnSpanSelect := OnRX2SampleRateSelect; + FSampleRateOverlayRX2.OnHidePanel := OnRX2SampleRateOverlayAction; + FSampleRateOverlayRX2.OnInvalidate := OnRX2VfoOverlayInvalidate; + FSampleRateOverlayRX2.FirstButtonCaption := '^'; + FSampleRateOverlayRX2.SetCurrentRate(FRX2SampleRate); + FSpecViewRX2.SampleRateOverlay := FSampleRateOverlayRX2; // S-метр — внутри PanelToolbar, справа PanelSMeterRight := TPanel.Create(Self); @@ -1985,12 +2105,43 @@ begin end; PbSpectrum.Parent := PanelRight; + if FUseOpenGLSpectrum then + begin + PbSpectrumRX2 := TOpenGLControl.Create(Self); + TOpenGLControl(PbSpectrumRX2).AutoResizeViewport := False; + TOpenGLControl(PbSpectrumRX2).DoubleBuffered := True; + TOpenGLControl(PbSpectrumRX2).OnPaint := FSpecViewRX2.PaintSpectrum; + TOpenGLControl(PbSpectrumRX2).OnMouseDown := PbSpectrumMouseDown; + TOpenGLControl(PbSpectrumRX2).OnMouseMove := PbSpectrumMouseMove; + TOpenGLControl(PbSpectrumRX2).OnMouseUp := PbSpectrumMouseUp; + TOpenGLControl(PbSpectrumRX2).OnMouseLeave := PbSpectrumMouseLeave; + TSpectrumViewOpenGL(FSpecViewRX2).AttachControl(TOpenGLControl(PbSpectrumRX2)); + end + else + begin + PbSpectrumRX2 := TPaintBox.Create(Self); + TPaintBox(PbSpectrumRX2).OnPaint := FSpecViewRX2.PaintSpectrum; + TPaintBox(PbSpectrumRX2).OnMouseDown := PbSpectrumMouseDown; + TPaintBox(PbSpectrumRX2).OnMouseMove := PbSpectrumMouseMove; + TPaintBox(PbSpectrumRX2).OnMouseUp := PbSpectrumMouseUp; + TPaintBox(PbSpectrumRX2).OnMouseLeave := PbSpectrumMouseLeave; + end; + PbSpectrumRX2.Parent := PanelRight; + PbSpectrumRX2.Visible := False; + PbRuler := TPaintBox.Create(Self); PbRuler.Parent := PanelRight; PbRuler.OnPaint := FSpecView.PaintRuler; PbRuler.Cursor := crDefault; FSpecView.PbRuler := PbRuler; + PbRulerRX2 := TPaintBox.Create(Self); + PbRulerRX2.Parent := PanelRight; + PbRulerRX2.OnPaint := FSpecViewRX2.PaintRuler; + PbRulerRX2.Cursor := crDefault; + PbRulerRX2.Visible := False; + FSpecViewRX2.PbRuler := PbRulerRX2; + // ---- Splitter между спектром+линейкой и водопадом ---- PanelSplitter := TPanel.Create(Self); PanelSplitter.Parent := PanelRight; @@ -2023,6 +2174,28 @@ begin end; PbWaterfall.Parent := PanelRight; + if FUseOpenGLSpectrum then + begin + PbWaterfallRX2 := TOpenGLControl.Create(Self); + TOpenGLControl(PbWaterfallRX2).AutoResizeViewport := False; + TOpenGLControl(PbWaterfallRX2).DoubleBuffered := True; + TOpenGLControl(PbWaterfallRX2).OnPaint := FSpecViewRX2.PaintWaterfall; + TOpenGLControl(PbWaterfallRX2).OnMouseDown := PbWaterfallMouseDown; + TOpenGLControl(PbWaterfallRX2).OnMouseMove := PbWaterfallMouseMove; + TOpenGLControl(PbWaterfallRX2).OnMouseUp := PbWaterfallMouseUp; + TSpectrumViewOpenGL(FSpecViewRX2).AttachWaterfallControl(TOpenGLControl(PbWaterfallRX2)); + end + else + begin + PbWaterfallRX2 := TPaintBox.Create(Self); + TPaintBox(PbWaterfallRX2).OnPaint := FSpecViewRX2.PaintWaterfall; + TPaintBox(PbWaterfallRX2).OnMouseDown := PbWaterfallMouseDown; + TPaintBox(PbWaterfallRX2).OnMouseMove := PbWaterfallMouseMove; + TPaintBox(PbWaterfallRX2).OnMouseUp := PbWaterfallMouseUp; + end; + PbWaterfallRX2.Parent := PanelRight; + PbWaterfallRX2.Visible := False; + // Initial layout ResizeSpectrumPanels; end; @@ -2310,10 +2483,13 @@ const MIN_WH = 40; // минимальная высота водопада var RW, RH, SH, WH, TopOff, WideH, WideSpecH: Integer; - AvailH: Integer; + AvailH, PaneH, RX2Top, RX2SH, RX2WH: Integer; RULER_H: Integer; begin RULER_H := MulDiv(18, Screen.PixelsPerInch, 96); + PaneH := 0; + RX2SH := 0; + RX2WH := 0; if PanelRight = nil then Exit; if Assigned(FSpecView) then SyncSpecViewFreq; RW := PanelRight.ClientWidth; @@ -2426,10 +2602,20 @@ begin PbRuler.Visible := True; PanelSplitter.Visible := True; PbWaterfall.Visible := True; + if PbSpectrumRX2 <> nil then PbSpectrumRX2.Visible := FRX2Enabled; + if PbRulerRX2 <> nil then PbRulerRX2.Visible := FRX2Enabled; + if PbWaterfallRX2 <> nil then PbWaterfallRX2.Visible := FRX2Enabled; AvailH := RH - TopOff - RULER_H - SPLITTER_H; if AvailH < (MIN_SH + MIN_WH) then Exit; + if FRX2Enabled and not FRX2Floating then + begin + PaneH := (RH - TopOff) div 2; + AvailH := PaneH - RULER_H - SPLITTER_H; + if AvailH < (MIN_SH + MIN_WH) then AvailH := MIN_SH + MIN_WH; + end; + // Ограничиваем соотношение, чтобы каждая зона имела минимальный размер if FSplitterRatio < MIN_SH / AvailH then FSplitterRatio := MIN_SH / AvailH; @@ -2449,6 +2635,39 @@ begin // Водопад — под сплиттером PbWaterfall.SetBounds(0, TopOff + SH + RULER_H + SPLITTER_H, RW, WH); + if FRX2Enabled and not FRX2Floating then + begin + RX2Top := TopOff + PaneH; + RX2SH := SH; + RX2WH := Max(MIN_WH, RH - RX2Top - RX2SH - RULER_H); + PbSpectrumRX2.SetBounds(0, RX2Top, RW, RX2SH); + PbRulerRX2.SetBounds(0, RX2Top + RX2SH, RW, RULER_H); + PbWaterfallRX2.SetBounds(0, RX2Top + RX2SH + RULER_H, RW, RX2WH); + FVfoOverlayRX2.Width := 260; + FVfoOverlayRX2.Height := 136; + FSampleRateOverlayRX2.SetBounds(4, 4, Max(0, RW - 8), 26); + PositionRX2VfoOverlay; + end + else if FRX2Enabled and FRX2Floating and (FRX2Form <> nil) then + begin + RX2Top := 0; + RX2SH := Max(MIN_SH, (FRX2Form.ClientHeight - RULER_H) div 2); + RX2WH := Max(MIN_WH, FRX2Form.ClientHeight - RX2SH - RULER_H); + PbSpectrumRX2.SetBounds(0, RX2Top, FRX2Form.ClientWidth, RX2SH); + PbRulerRX2.SetBounds(0, RX2Top + RX2SH, FRX2Form.ClientWidth, RULER_H); + PbWaterfallRX2.SetBounds(0, RX2Top + RX2SH + RULER_H, FRX2Form.ClientWidth, RX2WH); + FVfoOverlayRX2.Width := 260; + FVfoOverlayRX2.Height := 136; + FSampleRateOverlayRX2.SetBounds(4, 4, Max(0, FRX2Form.ClientWidth - 8), 26); + PositionRX2VfoOverlay; + end + else + begin + if PbSpectrumRX2 <> nil then PbSpectrumRX2.Visible := False; + if PbRulerRX2 <> nil then PbRulerRX2.Visible := False; + if PbWaterfallRX2 <> nil then PbWaterfallRX2.Visible := False; + end; + PositionSampleRateOverlay; // Обновляем переменные размеров @@ -2462,10 +2681,23 @@ begin if SH > 0 then FSpecView.SetSpectrumBitmapSize(RW, SH); if WH > 0 then FSpecView.SetWaterfallBitmapSize(RW, WH); FSpecView.SetRulerSize(RW, RULER_H); + if FRX2Enabled then + begin + FSpecViewRX2.SetSpectrumBitmapSize(RW, RX2SH); + FSpecViewRX2.SetWaterfallBitmapSize(RW, RX2WH); + FSpecViewRX2.SetRulerSize(RW, RULER_H); + end; FSpecView.DrawSpectrum; PbSpectrum.Invalidate; if PbRuler <> nil then PbRuler.Invalidate; PbWaterfall.Invalidate; + if FRX2Enabled then + begin + FSpecViewRX2.DrawSpectrum; + PbSpectrumRX2.Invalidate; + PbRulerRX2.Invalidate; + PbWaterfallRX2.Invalidate; + end; end; end; @@ -2496,6 +2728,7 @@ begin if PanelFMCTCSS <> nil then DP(PanelFMCTCSS); if PanelFMStep <> nil then DP(PanelFMStep); DP(PanelRX); DP(PanelTX); + if PanelRXSwitch <> nil then DP(PanelRXSwitch); DP(PanelRight); if PbSpectrum is TPaintBox then @@ -2531,6 +2764,7 @@ begin for i := 0 to 4 do StyleButton(BtnAGCMode[i],BtnAGCMode[i].Active); StyleButton(BtnCTun, BtnCTun.Active); StyleButton(BtnDUP, BtnDUP.Active); + if BtnRX2 <> nil then StyleButton(BtnRX2, BtnRX2.Active); if BtnFMSQ <> nil then StyleButton(BtnFMSQ, BtnFMSQ.Active); if TrkFMSQ <> nil then DS(TrkFMSQ); if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, BtnFMCTCSS.Active); @@ -2555,6 +2789,7 @@ begin // Спектр FSpecView.SetTheme(T); + if Assigned(FSpecViewRX2) then FSpecViewRX2.SetTheme(T); if Assigned(FWidebandView) then FWidebandView.SetTheme(T); // VFO display — вызывает UpdateVfoDisplay, который использует тему @@ -2702,9 +2937,15 @@ begin if FActiveVfo = 0 then begin FreqDispA.ColorNormal := T.FreqActive; - FreqDispB.ColorNormal := T.FreqDim; + if FRX2Enabled then + FreqDispB.ColorNormal := T.FreqActive + else + FreqDispB.ColorNormal := T.FreqDim; LblVfoALabel.Font.Color := T.FreqActive; - LblVfoBLabel.Font.Color := T.TextDim; + if FRX2Enabled then + LblVfoBLabel.Font.Color := T.FreqActive + else + LblVfoBLabel.Font.Color := T.TextDim; if BtnTopVfoASelect <> nil then begin BtnTopVfoASelect.ClrNorm := T.VfoSelActNorm; BtnTopVfoASelect.ClrActive := T.VfoSelActNorm; @@ -2716,13 +2957,26 @@ begin BtnTopVfoASelect.Invalidate; end; if BtnTopVfoBSelect <> nil then begin - BtnTopVfoBSelect.ClrNorm := T.VfoSelInaNorm; - BtnTopVfoBSelect.ClrActive := T.VfoSelInaNorm; - BtnTopVfoBSelect.ClrHot := T.VfoSelInaNorm; - BtnTopVfoBSelect.ClrBorder := T.VfoSelInaBorder; - BtnTopVfoBSelect.ClrText := T.VfoSelInaText; - BtnTopVfoBSelect.ClrTextAct := T.VfoSelInaTextAct; - BtnTopVfoBSelect.Active := False; + if FRX2Enabled then + begin + BtnTopVfoBSelect.ClrNorm := T.VfoSelActNorm; + BtnTopVfoBSelect.ClrActive := T.VfoSelActNorm; + BtnTopVfoBSelect.ClrHot := T.VfoSelActNorm; + BtnTopVfoBSelect.ClrBorder := T.VfoSelActBorder; + BtnTopVfoBSelect.ClrText := T.VfoSelActText; + BtnTopVfoBSelect.ClrTextAct := T.VfoSelActTextAct; + BtnTopVfoBSelect.Active := True; + end + else + begin + BtnTopVfoBSelect.ClrNorm := T.VfoSelInaNorm; + BtnTopVfoBSelect.ClrActive := T.VfoSelInaNorm; + BtnTopVfoBSelect.ClrHot := T.VfoSelInaNorm; + BtnTopVfoBSelect.ClrBorder := T.VfoSelInaBorder; + BtnTopVfoBSelect.ClrText := T.VfoSelInaText; + BtnTopVfoBSelect.ClrTextAct := T.VfoSelInaTextAct; + BtnTopVfoBSelect.Active := False; + end; BtnTopVfoBSelect.Invalidate; end; end else begin @@ -2812,6 +3066,8 @@ end; procedure TMainForm.InvalidateGridCache; begin FSpecView.InvalidateGridCache; + if Assigned(FSpecViewRX2) then + FSpecViewRX2.InvalidateGridCache; end; procedure TMainForm.SyncSpecViewFreq; @@ -2823,6 +3079,16 @@ begin FSpecView.SpanHz := FSpanHz; FSpecView.Mode := FMode; FSpecView.FilterBW := FFilterBW; + if Assigned(FSpecViewRX2) then + begin + FSpecViewRX2.VfoA := FRX2Vfo; + FSpecViewRX2.VfoB := FRX2Vfo; + FSpecViewRX2.ActiveVfo := 0; + FSpecViewRX2.CenterFreq := FRX2CenterFreq; + FSpecViewRX2.SpanHz := FRX2SpanHz; + FSpecViewRX2.Mode := FRX2Mode; + FSpecViewRX2.FilterBW := FRX2FilterBW; + end; if FMode = MODE_FM then FSpecView.FMGridStepHz := FM_STEP_HZ[FFMStepIdx] else @@ -2837,6 +3103,17 @@ begin PbWideband.Invalidate; end; +function TMainForm.CurrentBoardSupportsRX2: Boolean; +begin + Result := + (FNetwork <> nil) and FNetwork.Connected and + ((FNetwork.Device.NumDDCs >= 2) or + (FNetwork.Device.BoardType in [ + BOARD_HERMES, BOARD_HERMES_10E, + BOARD_ANGELA, BOARD_ORION, BOARD_ORION_MK2, + BOARD_SATURN + ])); +end; // =========================================================================== // Timers @@ -2986,6 +3263,15 @@ begin FVfoOverlay.UpdateVfo(FVfoA); PositionVfoOverlay; end; + if FRX2Enabled and Assigned(FVfoOverlayRX2) then + begin + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized and (PbSpectrumRX2 <> nil) then + FDSPEngineRX2.SetSpectrumWidth(PbSpectrumRX2.Width); + FRX2LastSMeter := FDSPEngineRX2.SMeter; + FVfoOverlayRX2.UpdateSMeter(FRX2LastSMeter); + FVfoOverlayRX2.UpdateVfo(FRX2Vfo); + PositionRX2VfoOverlay; + end; // Панорама и водопад теперь рисуются независимо по факту новых кадров. // Это лучше совпадает с Thetis: маленький FFT даёт новые строки чаще. if FSpectrumDirty then @@ -3007,6 +3293,19 @@ begin PbWideband.Invalidate; FWidebandDirty := False; end; + if FRX2Enabled and FRX2SpectrumDirty then + begin + FSpecViewRX2.DrawSpectrum; + PbSpectrumRX2.Invalidate; + if FSpecViewRX2.NeedsRulerRedraw then PbRulerRX2.Invalidate; + FRX2SpectrumDirty := False; + end; + if FRX2Enabled and FRX2WaterfallDirty then + begin + FSpecViewRX2.DrawWaterfall; + PbWaterfallRX2.Invalidate; + FRX2WaterfallDirty := False; + end; end else begin @@ -3419,6 +3718,14 @@ begin if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame); end; + if FRX2Enabled and (DDCIndex = FRX2DDC) and Assigned(FDSPEngineRX2) and + FDSPEngineRX2.Initialized then + begin + SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or + Integer(Data.SamplesPerFrame[1]); + if SamplesPerFrame <= 0 then SamplesPerFrame := 238; + FDSPEngineRX2.PushDDCPacket(Data.IQData, 0, SamplesPerFrame); + end; // 2. Запоминаем последний seq — статус обновит таймер (без Synchronize в горячем пути) FLastDDCSeq := Seq; @@ -3679,6 +3986,17 @@ begin FDSPEngine.OnSpectrum := OnSpectrumReady; FDSPEngine.OnWaterfall := OnWaterfallReady; FDSPEngine.OnTXIQ := OnTXIQReady; + FDSPEngine.OnPullMicSamples := PullSoundCardMic; + + if Assigned(FDSPEngineRX2) then + begin + FDSPEngineRX2.Close; + FreeAndNil(FDSPEngineRX2); + end; + if FRX2SampleRate <= 0 then FRX2SampleRate := ASampleRate; + FDSPEngineRX2 := TWDSPEngine.Create(FRX2SampleRate, 48000, 512, 2, -1, 2, -1, False); + FDSPEngineRX2.OnSpectrum := OnRX2SpectrumReady; + FDSPEngineRX2.OnWaterfall := OnRX2WaterfallReady; end; function TMainForm.EnsureWDSPWisdom: Boolean; @@ -3911,6 +4229,8 @@ begin 'Put wdsp.dll рядом с ewsdr.exe (той же разрядности, что и приложение).'); Exit; end; + if FRX2Enabled and Assigned(FDSPEngineRX2) and (not FDSPEngineRX2.Initialized) then + FDSPEngineRX2.Open; DoConnectDevice(FPendingDev); end; @@ -3967,8 +4287,15 @@ begin begin FSampleRate := G_Settings.SampleRate; FSpanHz := FSampleRate; + if not FRX2Enabled then + begin + FRX2SampleRate := FSampleRate; + FRX2SpanHz := FRX2SampleRate; + end; if Assigned(FSampleRateOverlay) then FSampleRateOverlay.SetCurrentRate(FSampleRate); + if Assigned(FSampleRateOverlayRX2) then + FSampleRateOverlayRX2.SetCurrentRate(FRX2SampleRate); end; BtnNR.Tag := EnsureRange(G_Settings.NRMode, 0, 4); UpdateNRButton; BtnNB.Tag := EnsureRange(G_Settings.NBMode, 0, 2); UpdateNBButton; @@ -3987,6 +4314,12 @@ begin FSpecView.WfManualLow := FWfManualLow; FSpecView.WfAGCOffset := FWfAGCOffset; FSpecView.ResetWfAvgBuf; + FSpecViewRX2.WfAGCEnabled := FWfAGCEnabled; + FSpecViewRX2.WfNFEnabled := FWfNFEnabled; + FSpecViewRX2.WfManualHigh := FWfManualHigh; + FSpecViewRX2.WfManualLow := FWfManualLow; + FSpecViewRX2.WfAGCOffset := FWfAGCOffset; + FSpecViewRX2.ResetWfAvgBuf; FShowSpectrum := G_Settings.ShowSpectrum; FShowWaterfall := G_Settings.ShowWaterfall; FShowWideband := G_Settings.ShowWideband; @@ -4007,6 +4340,9 @@ begin FSpecView.SpecRefLevel := FSpecRefLevel; FSpecView.SpecRange := FSpecRange; FSpecView.SpecGridStep := FSpecGridStep; + FSpecViewRX2.SpecRefLevel := FSpecRefLevel; + FSpecViewRX2.SpecRange := FSpecRange; + FSpecViewRX2.SpecGridStep := FSpecGridStep; InvalidateGridCache; if FWDSPReady then begin @@ -4015,6 +4351,14 @@ begin G_Settings.SpecAvgTimeMS); FDSPEngine.SetWaterfallDisplay(G_Settings.WfDetector, G_Settings.WfAvgMode, G_Settings.WfAvgTimeMS); + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + FDSPEngineRX2.SetFFTParams(G_Settings.FFTSize, G_Settings.WindowType); + FDSPEngineRX2.SetSpectrumDisplay(G_Settings.SpecDetector, G_Settings.SpecAvgMode, + G_Settings.SpecAvgTimeMS); + FDSPEngineRX2.SetWaterfallDisplay(G_Settings.WfDetector, G_Settings.WfAvgMode, + G_Settings.WfAvgTimeMS); + end; end; // Аудио устройство вывода FAudioOut.Close; @@ -4079,10 +4423,18 @@ begin FNetwork.SendGeneralPacket(GenPkt); FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); - if FNetwork.Device.BoardType in [3, 4, 5] then + if FNetwork.Device.BoardType in [3, 4, 5, 10] then FActiveDDC := 2 else FActiveDDC := 0; + FRX2DDC := FActiveDDC + 1; + FRX2Available := CurrentBoardSupportsRX2; + if BtnRX2 <> nil then + begin + BtnRX2.Enabled := FRX2Available; + StyleButton(BtnRX2, FRX2Enabled); + BtnRX2.Invalidate; + end; // Синхронизируем samplerate WDSP если нужно if FWDSPReady and (FDSPEngine.SampleRate <> FSampleRate) then @@ -4122,7 +4474,12 @@ begin // DDC и DUC Specific — теперь потоки эмулятора слушают на своих портах. // DUC Specific содержит mic-конфигурацию (boost/bias/line/PTT) из FTXSettings. - FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); + if FRX2Enabled and FRX2Available then + FNetwork.ConfigureDDCReceivers(2, FSampleRate div 1000, 0, + IfThen(FNetwork.Device.BoardType in [3, 4, 5, 10], 1, 0), + FDitherEnabled, FRandomEnabled, FRX2SampleRate div 1000) + else + FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); SendDUCSpecificFromSettings; // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; @@ -4246,6 +4603,7 @@ end; procedure TMainForm.FreqDispBClick(Sender: TObject); begin + if FRX2Enabled then Exit; // Клик на VFO-B → активируем B (перестройка на FVfoB) if FActiveVfo <> 1 then ActivateVfo(1); @@ -4272,12 +4630,18 @@ end; procedure TMainForm.BtnTopVfoBSelectClick(Sender: TObject); begin + if FRX2Enabled then + begin + UpdateVfoDisplay; + Exit; + end; if FActiveVfo <> 1 then ActivateVfo(1); end; procedure TMainForm.BtnTopVfoBTXClick(Sender: TObject); begin + if FRX2Enabled then Exit; FSplitTxB := True; if FSplitTxB and (FActiveVfo <> 0) then ActivateVfo(0) @@ -4304,6 +4668,12 @@ procedure TMainForm.FreqDispBChanged(Sender: TObject; NewFreq: Int64); var BandIdx: Integer; begin + if FRX2Enabled then + begin + ApplyRX2Vfo(NewFreq); + Exit; + end; + FVfoB := NewFreq; if FActiveVfo = 1 then begin @@ -4542,6 +4912,35 @@ begin end; end; +procedure TMainForm.ApplyRX2Vfo(NewFreq: Int64); +begin + FRX2Vfo := NewFreq; + FRX2CenterFreq := FRX2Vfo; + if Assigned(FreqDispB) then + FreqDispB.Frequency := Round(FRX2Vfo); + if Assigned(FSpecViewRX2) then + begin + FSpecViewRX2.VfoA := FRX2Vfo; + FSpecViewRX2.VfoB := FRX2Vfo; + FSpecViewRX2.CenterFreq := FRX2CenterFreq; + FSpecViewRX2.InvalidateRulerCache; + end; + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + FDSPEngineRX2.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + FNetwork.SetDDCFrequency(FRX2DDC, XvtrTranslate(FRX2CenterFreq)); + if Assigned(FVfoOverlayRX2) then + begin + FVfoOverlayRX2.UpdateVfo(FRX2Vfo); + PositionRX2VfoOverlay; + end; + FRX2SpectrumDirty := True; + FRX2WaterfallDirty := True; + if Assigned(PbSpectrumRX2) then PbSpectrumRX2.Invalidate; + if Assigned(PbRulerRX2) then PbRulerRX2.Invalidate; + if Assigned(PbWaterfallRX2) then PbWaterfallRX2.Invalidate; +end; + // --------------------------------------------------------------------------- // CTUN toggle // --------------------------------------------------------------------------- @@ -4603,6 +5002,66 @@ begin ApplyDUP(not FDisplayDuplex); end; +procedure TMainForm.BtnRX2Click(Sender: TObject); +begin + FRX2Available := CurrentBoardSupportsRX2; + if not FRX2Available then Exit; + FRX2Enabled := not FRX2Enabled; + StyleButton(BtnRX2, FRX2Enabled); + BtnRX2.Invalidate; + if FRX2Enabled then + begin + if FActiveVfo <> 0 then + begin + FVfoA := FCenterFreq; + FActiveVfo := 0; + end; + FSplitTxB := False; + FRX2Vfo := FVfoB; + FRX2CenterFreq := FRX2Vfo; + FRX2Mode := FMode; + FRX2FilterBW := FFilterBW; + FVfoOverlayRX2.SetState(FRX2Mode, FRX2FilterBW, FRX2Vfo, FRX2LastSMeter); + FVfoOverlayRX2.SetDSPState(0, 0, False, False, FRX2AGCMode); + if Assigned(FDSPEngineRX2) and FWDSPReady and (not FDSPEngineRX2.Initialized) then + FDSPEngineRX2.Open; + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + if FDSPEngineRX2.SampleRate <> FRX2SampleRate then + FDSPEngineRX2.ChangeSampleRate(FRX2SampleRate); + FDSPEngineRX2.SetFFTParams(FDSPEngine.FFTSize, FDSPEngine.WindowType); + FDSPEngineRX2.SetSpectrumDisplay(FDSPEngine.SpecDetector, FDSPEngine.SpecAvgMode, + FDSPEngine.SpecAvgTimeMS); + FDSPEngineRX2.SetWaterfallDisplay(FDSPEngine.WfDetector, FDSPEngine.WfAvgMode, + FDSPEngine.WfAvgTimeMS); + FDSPEngineRX2.SetDisplayFPS(FDisplayFPS); + FDSPEngineRX2.SetAGCTop(FAGCTop); + end; + OnRX2ModeFilterSelect(FRX2Mode, FRX2FilterBW); + UpdateVfoDisplay; + end + else if Assigned(FDSPEngineRX2) then + begin + FDSPEngineRX2.FlushRX; + UpdateVfoDisplay; + end; + + if FNetwork.Connected and FRunning then + if FRX2Enabled then + FNetwork.ConfigureDDCReceivers(2, FSampleRate div 1000, 0, + IfThen(FNetwork.Device.BoardType in [3, 4, 5, 10], 1, 0), + FDitherEnabled, FRandomEnabled, FRX2SampleRate div 1000) + else + FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); + if FNetwork.Connected then + begin + FNetwork.SetDDCFrequency(FActiveDDC, XvtrTranslate(FCenterFreq)); + if FRX2Enabled then + FNetwork.SetDDCFrequency(FRX2DDC, XvtrTranslate(FRX2CenterFreq)); + end; + ResizeSpectrumPanels; +end; + // --------------------------------------------------------------------------- // TUN — Thetis-style тон: WDSP TXA PostGen + MOX с отдельным уровнем Drive // --------------------------------------------------------------------------- @@ -4843,10 +5302,102 @@ begin end; end; +procedure TMainForm.DoRX2SpectrumClick(PixelX: Integer; PanelWidth: Integer); +var + ClickFreq: Int64; + StepHz: Int64; +begin + if PanelWidth <= 0 then Exit; + if (FRX2Mode = MODE_FM) and FFMStepOn then + StepHz := FM_STEP_HZ[FFMStepIdx] + else + StepHz := 100; + ClickFreq := Round(FRX2CenterFreq + (PixelX / PanelWidth - 0.5) * FRX2SpanHz); + ClickFreq := ((ClickFreq + StepHz div 2) div StepHz) * StepHz; + + FRX2Vfo := ClickFreq; + FRX2CenterFreq := ClickFreq; + if Assigned(FreqDispB) then + FreqDispB.Frequency := Round(FRX2Vfo); + FSpecViewRX2.VfoA := FRX2Vfo; + FSpecViewRX2.VfoB := FRX2Vfo; + FSpecViewRX2.CenterFreq := FRX2CenterFreq; + if FNetwork.Connected and FNetwork.Running then + FNetwork.SetDDCFrequency(FRX2DDC, XvtrTranslate(FRX2CenterFreq)); + FVfoOverlayRX2.UpdateVfo(FRX2Vfo); + PositionRX2VfoOverlay; + FSpecViewRX2.InvalidateRulerCache; + FRX2SpectrumDirty := True; + FRX2WaterfallDirty := True; + if PbSpectrumRX2 <> nil then PbSpectrumRX2.Invalidate; + if PbRulerRX2 <> nil then PbRulerRX2.Invalidate; + if PbWaterfallRX2 <> nil then PbWaterfallRX2.Invalidate; +end; + +procedure TMainForm.DoRX2SpectrumDrag(PixelX: Integer; PanelWidth: Integer); +var + dPix: Integer; + dFreq: Double; +begin + if PanelWidth <= 0 then Exit; + dPix := PixelX - FSpecDragX0; + dFreq := dPix / PanelWidth * FRX2SpanHz; + + FRX2Vfo := FSpecDragFreq - dFreq; + FRX2CenterFreq := FRX2Vfo; + if Assigned(FreqDispB) then + FreqDispB.Frequency := Round(FRX2Vfo); + FSpecViewRX2.VfoA := FRX2Vfo; + FSpecViewRX2.VfoB := FRX2Vfo; + FSpecViewRX2.CenterFreq := FRX2CenterFreq; + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + FDSPEngineRX2.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + FNetwork.SetDDCFrequency(FRX2DDC, XvtrTranslate(FRX2CenterFreq)); + FVfoOverlayRX2.UpdateVfo(FRX2Vfo); + PositionRX2VfoOverlay; + FSpecViewRX2.InvalidateRulerCache; + FRX2SpectrumDirty := True; + FRX2WaterfallDirty := True; +end; + // Спектр procedure TMainForm.PbSpectrumMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbSpectrumRX2 then + begin + if Assigned(FVfoOverlayRX2) and FVfoOverlayRX2.HandleMouseDown(Button, X, Y) then Exit; + if Assigned(FSampleRateOverlayRX2) and FSampleRateOverlayRX2.HandleMouseDown(Button, X, Y) then Exit; + if Button = mbLeft then + begin + if FSpecViewRX2.MarkerActive then + begin + FSpecViewRX2.MarkerActive := False; + PbSpectrumRX2.Invalidate; + PbWaterfallRX2.Invalidate; + end; + FSpecDrag := True; + FSpecDragX0 := X; + FSpecDragFreq := FRX2Vfo; + PbSpectrumRX2.Cursor := crSizeWE; + end + else if Button = mbRight then + begin + if FSpecViewRX2.MarkerActive and + (Abs(Round(X / Max(1, PbSpectrumRX2.Width) * 1000) - FSpecViewRX2.MarkerX) < 8) then + FSpecViewRX2.MarkerActive := False + else + begin + FSpecViewRX2.MarkerActive := True; + FSpecViewRX2.MarkerX := Round(X / Max(1, PbSpectrumRX2.Width) * 1000); + end; + PbSpectrumRX2.Invalidate; + PbWaterfallRX2.Invalidate; + end; + Exit; + end; + if Assigned(FVfoOverlay) and FVfoOverlay.HandleMouseDown(Button, X, Y) then Exit; if Assigned(FSampleRateOverlay) and @@ -4899,6 +5450,28 @@ end; procedure TMainForm.PbSpectrumMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbSpectrumRX2 then + begin + if Assigned(FSampleRateOverlayRX2) then FSampleRateOverlayRX2.HandleMouseMove(X, Y); + if Assigned(FVfoOverlayRX2) then FVfoOverlayRX2.HandleMouseMove(X, Y); + if not FSpecDrag then + begin + if (Assigned(FVfoOverlayRX2) and (FVfoOverlayRX2.HotIdx >= 0)) or + (Assigned(FSampleRateOverlayRX2) and (FSampleRateOverlayRX2.HotIdx >= 0)) then + PbSpectrumRX2.Cursor := crHandPoint + else + PbSpectrumRX2.Cursor := crDefault; + end; + if FSpecDrag and (ssLeft in Shift) then + DoRX2SpectrumDrag(X, PbSpectrumRX2.Width); + if FSpecViewRX2.MarkerActive then + begin + FSpecViewRX2.MarkerX := Round(X / Max(1, PbSpectrumRX2.Width) * 1000); + FRX2SpectrumDirty := True; + end; + Exit; + end; + if Assigned(FSampleRateOverlay) then begin FSampleRateOverlay.HandleMouseMove(X, Y); @@ -4927,6 +5500,18 @@ end; procedure TMainForm.PbSpectrumMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbSpectrumRX2 then + begin + if Button = mbLeft then + begin + if Abs(X - FSpecDragX0) < 4 then + DoRX2SpectrumClick(X, PbSpectrumRX2.Width); + FSpecDrag := False; + PbSpectrumRX2.Cursor := crDefault; + end; + Exit; + end; + if Button = mbLeft then begin if (Abs(X - FSpecDragX0) < 4) then @@ -4938,6 +5523,14 @@ end; procedure TMainForm.PbSpectrumMouseLeave(Sender: TObject); begin + if Sender = PbSpectrumRX2 then + begin + if Assigned(FSampleRateOverlayRX2) then FSampleRateOverlayRX2.HandleMouseLeave; + if Assigned(FVfoOverlayRX2) then FVfoOverlayRX2.HandleMouseLeave; + if not FSpecDrag then PbSpectrumRX2.Cursor := crDefault; + Exit; + end; + if Assigned(FSampleRateOverlay) then FSampleRateOverlay.HandleMouseLeave; if Assigned(FVfoOverlay) then @@ -4949,6 +5542,37 @@ end; procedure TMainForm.PbWaterfallMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbWaterfallRX2 then + begin + if Button = mbLeft then + begin + if FSpecViewRX2.MarkerActive then + begin + FSpecViewRX2.MarkerActive := False; + PbSpectrumRX2.Invalidate; + PbWaterfallRX2.Invalidate; + end; + FSpecDrag := True; + FSpecDragX0 := X; + FSpecDragFreq := FRX2Vfo; + PbWaterfallRX2.Cursor := crSizeWE; + end + else if Button = mbRight then + begin + if FSpecViewRX2.MarkerActive and + (Abs(Round(X / Max(1, PbWaterfallRX2.Width) * 1000) - FSpecViewRX2.MarkerX) < 8) then + FSpecViewRX2.MarkerActive := False + else + begin + FSpecViewRX2.MarkerActive := True; + FSpecViewRX2.MarkerX := Round(X / Max(1, PbWaterfallRX2.Width) * 1000); + end; + PbSpectrumRX2.Invalidate; + PbWaterfallRX2.Invalidate; + end; + Exit; + end; + if Button = mbLeft then begin if FSpecView.MarkerActive then @@ -4983,6 +5607,18 @@ end; procedure TMainForm.PbWaterfallMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbWaterfallRX2 then + begin + if FSpecDrag and (ssLeft in Shift) then + DoRX2SpectrumDrag(X, PbWaterfallRX2.Width); + if FSpecViewRX2.MarkerActive then + begin + FSpecViewRX2.MarkerX := Round(X / Max(1, PbWaterfallRX2.Width) * 1000); + FRX2SpectrumDirty := True; + end; + Exit; + end; + if FSpecDrag and (ssLeft in Shift) then DoSpectrumDrag(X, PbWaterfall.Width); if FSpecView.MarkerActive then @@ -4995,6 +5631,18 @@ end; procedure TMainForm.PbWaterfallMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin + if Sender = PbWaterfallRX2 then + begin + if Button = mbLeft then + begin + if Abs(X - FSpecDragX0) < 4 then + DoRX2SpectrumClick(X, PbWaterfallRX2.Width); + FSpecDrag := False; + PbWaterfallRX2.Cursor := crDefault; + end; + Exit; + end; + if Button = mbLeft then begin if (Abs(X - FSpecDragX0) < 4) then @@ -5203,6 +5851,16 @@ var BandIdx: Integer; ActiveFreq: Int64; begin + if FRX2Enabled then + begin + Tmp := FVfoA; + FVfoA := FRX2Vfo; + ApplyVfoA(Round(FVfoA)); + ApplyRX2Vfo(Round(Tmp)); + UpdateVfoDisplay; + Exit; + end; + // Меняем частоты местами Tmp := FVfoA; FVfoA := FVfoB; FVfoB := Tmp; @@ -5238,6 +5896,13 @@ end; procedure TMainForm.BtnVfoACopyBClick(Sender: TObject); begin + if FRX2Enabled then + begin + ApplyRX2Vfo(Round(FVfoA)); + UpdateVfoDisplay; + Exit; + end; + // A>B: копируем частоту A в B (VFO-B получает частоту VFO-A) FVfoB := FVfoA; if FActiveVfo = 1 then @@ -5261,6 +5926,19 @@ end; procedure TMainForm.BtnVfoBCopyAClick(Sender: TObject); begin + if FRX2Enabled then + begin + FVfoA := FRX2Vfo; + if FActiveVfo = 0 then + ApplyVfoA(Round(FVfoA)) + else + begin + FreqDispA.Frequency := Round(FVfoA); + UpdateVfoDisplay; + end; + Exit; + end; + // B>A: копируем частоту B в A (VFO-A получает частоту VFO-B) FVfoA := FVfoB; if FActiveVfo = 0 then @@ -5483,6 +6161,14 @@ begin FWfNFEnabled := WfNF; FSpecView.WfAGCEnabled := FWfAGCEnabled; FSpecView.WfNFEnabled := FWfNFEnabled; + FSpecViewRX2.WfAGCEnabled := FWfAGCEnabled; + FSpecViewRX2.WfNFEnabled := FWfNFEnabled; + FSpecView.ResetWfAvgBuf; + FSpecViewRX2.ResetWfAvgBuf; + FWaterfallDirty := True; + FRX2WaterfallDirty := True; + if Assigned(PbWaterfall) then PbWaterfall.Invalidate; + if Assigned(PbWaterfallRX2) then PbWaterfallRX2.Invalidate; end; procedure TMainForm.ApplyADCSettings(Dither, Random: Boolean); @@ -5490,7 +6176,12 @@ begin FDitherEnabled := Dither; FRandomEnabled := Random; if FNetwork.Connected and FRunning then - FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); + if FRX2Enabled and FRX2Available then + FNetwork.ConfigureDDCReceivers(2, FSampleRate div 1000, 0, + IfThen(FNetwork.Device.BoardType in [3, 4, 5, 10], 1, 0), + FDitherEnabled, FRandomEnabled, FRX2SampleRate div 1000) + else + FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); if FDevConnected then FSettings.SaveGlobal(FDevMAC, MakeGlobalSettings); end; @@ -5577,6 +6268,43 @@ begin end; end; +procedure TMainForm.PositionRX2VfoOverlay; +var + VfoX, FilterEndX, OW, NewLeft, NewTop: Integer; + HiHz: Double; + SpecW: Integer; +begin + if not Assigned(FVfoOverlayRX2) or not Assigned(PbSpectrumRX2) then Exit; + OW := FVfoOverlayRX2.Width; + SpecW := PbSpectrumRX2.Width; + + if (SpecW > 0) and (FRX2SpanHz > 0) then + VfoX := Round((FRX2Vfo - FRX2CenterFreq + FRX2SpanHz / 2) / FRX2SpanHz * SpecW) + else + VfoX := SpecW div 2; + + case FRX2Mode of + MODE_LSB: HiHz := -100; + MODE_USB: HiHz := FRX2FilterBW; + else + HiHz := FRX2FilterBW / 2; + end; + if (SpecW > 0) and (FRX2SpanHz > 0) then + FilterEndX := VfoX + Round(HiHz / FRX2SpanHz * SpecW) + else + FilterEndX := VfoX; + FilterEndX := Max(0, Min(Max(0, SpecW - 1), FilterEndX)); + + NewLeft := Max(4, Min(Max(4, SpecW - OW - 4), FilterEndX + 6)); + NewTop := 16; + if (FVfoOverlayRX2.Left <> NewLeft) or (FVfoOverlayRX2.Top <> NewTop) then + begin + FVfoOverlayRX2.Left := NewLeft; + FVfoOverlayRX2.Top := NewTop; + OnRX2VfoOverlayInvalidate(FVfoOverlayRX2); + end; +end; + procedure TMainForm.PbSpectrumDblClick(Sender: TObject); begin // Двойной клик — ничего не делаем (оверлей всегда виден при скрытой панели) @@ -5646,6 +6374,101 @@ begin if Assigned(PbSpectrum) then PbSpectrum.Invalidate; end; +procedure TMainForm.OnRX2ModeFilterSelect(Mode: Integer; FilterBW: Integer); +begin + FRX2Mode := Mode; + FRX2FilterBW := FilterBW; + FSpecViewRX2.Mode := FRX2Mode; + FSpecViewRX2.FilterBW := FRX2FilterBW; + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + FDSPEngineRX2.SetMode(FRX2Mode); + case FRX2Mode of + MODE_LSB: FDSPEngineRX2.SetFilter(-FRX2FilterBW, -100); + MODE_USB: FDSPEngineRX2.SetFilter(100, FRX2FilterBW); + else + FDSPEngineRX2.SetFilter(-FRX2FilterBW div 2, FRX2FilterBW div 2); + end; + end; + FVfoOverlayRX2.SetState(FRX2Mode, FRX2FilterBW, FRX2Vfo, FRX2LastSMeter); + PositionRX2VfoOverlay; + OnRX2VfoOverlayInvalidate(FVfoOverlayRX2); +end; + +procedure TMainForm.OnRX2VfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean); +begin + if not Assigned(FDSPEngineRX2) then Exit; + FDSPEngineRX2.SetNRMode(NRMode); + FDSPEngineRX2.SetNBMode(NBMode); + FDSPEngineRX2.SetSNB(SNBOn); + FDSPEngineRX2.SetANF(ANFOn); +end; + +procedure TMainForm.OnRX2VfoOverlayAGCChange(AGCMode: Integer); +const + AGCModes: array[0..4] of TWDSPAGCMode = (agcFast, agcMedium, agcSlow, agcLong, agcOff); +begin + FRX2AGCMode := AGCMode; + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + FDSPEngineRX2.SetAGC(AGCModes[AGCMode], 50.0); +end; + +procedure TMainForm.OnRX2VfoOverlayInvalidate(Sender: TObject); +begin + FRX2SpectrumDirty := True; + if Assigned(FSpecViewRX2) then + begin + FSpecViewRX2.SpectrumDirty := True; + FSpecViewRX2.InvalidateOverlayCache; + end; + if Assigned(PbSpectrumRX2) then PbSpectrumRX2.Invalidate; +end; + +procedure TMainForm.OnRX2SampleRateOverlayAction; +begin + if not FRX2Enabled then Exit; + if not FRX2Floating then + begin + if FRX2Form = nil then + begin + FRX2Form := TForm.Create(Self); + FRX2Form.Caption := 'RX2'; + FRX2Form.Width := Max(640, PanelRight.ClientWidth); + FRX2Form.Height := Max(360, PanelRight.ClientHeight div 2); + FRX2Form.Position := poDesigned; + FRX2Form.OnClose := RX2FloatClose; + FRX2Form.OnResize := RightPanelResize; + end; + PbSpectrumRX2.Parent := FRX2Form; + PbRulerRX2.Parent := FRX2Form; + PbWaterfallRX2.Parent := FRX2Form; + FRX2Floating := True; + FRX2Form.Show; + end + else + begin + PbSpectrumRX2.Parent := PanelRight; + PbRulerRX2.Parent := PanelRight; + PbWaterfallRX2.Parent := PanelRight; + FRX2Floating := False; + if FRX2Form <> nil then FRX2Form.Hide; + end; + ResizeSpectrumPanels; +end; + +procedure TMainForm.RX2FloatClose(Sender: TObject; var CloseAction: TCloseAction); +begin + if FRX2Floating then + begin + PbSpectrumRX2.Parent := PanelRight; + PbRulerRX2.Parent := PanelRight; + PbWaterfallRX2.Parent := PanelRight; + FRX2Floating := False; + ResizeSpectrumPanels; + end; + CloseAction := caHide; +end; + procedure TMainForm.OnSampleRateSelect(SampleRate: Integer); var NewRate: Integer; @@ -5677,7 +6500,12 @@ begin // 2. Отправляем новый rate трансиверу // (после остановки DSP — новые пакеты сразу идут с правильным rate) if FNetwork.Connected then - FNetwork.ConfigureDDCs(1, DDCRate, 0, FDitherEnabled, FRandomEnabled); + if FRX2Enabled and FRX2Available then + FNetwork.ConfigureDDCReceivers(2, DDCRate, 0, + IfThen(FNetwork.Device.BoardType in [3, 4, 5, 10], 1, 0), + FDitherEnabled, FRandomEnabled, FRX2SampleRate div 1000) + else + FNetwork.ConfigureDDCs(1, DDCRate, 0, FDitherEnabled, FRandomEnabled); // 3. Восстанавливаем настройки DSP FWDSPReady := FDSPEngine.Initialized; @@ -5694,6 +6522,50 @@ begin if FNetwork.Connected then FDSPEngine.SetShift(FVfoA - FCenterFreq); end; + if FRX2Enabled and Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + FDSPEngineRX2.SetMode(FRX2Mode); + OnRX2ModeFilterSelect(FRX2Mode, FRX2FilterBW); + end; +end; + +procedure TMainForm.OnRX2SampleRateSelect(SampleRate: Integer); +var + NewRate: Integer; +begin + NewRate := SampleRate; + if NewRate = FRX2SampleRate then Exit; + + FRX2SampleRate := NewRate; + FRX2SpanHz := NewRate; + + if Assigned(FSampleRateOverlayRX2) then + FSampleRateOverlayRX2.SetCurrentRate(NewRate); + if Assigned(FSpecViewRX2) then + begin + FSpecViewRX2.SpanHz := FRX2SpanHz; + FSpecViewRX2.InvalidateRulerCache; + end; + Application.ProcessMessages; + + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + FDSPEngineRX2.ChangeSampleRate(NewRate); + FDSPEngineRX2.SetMode(FRX2Mode); + OnRX2ModeFilterSelect(FRX2Mode, FRX2FilterBW); + end; + + if FNetwork.Connected and FRunning then + if FRX2Enabled and FRX2Available then + FNetwork.ConfigureDDCReceivers(2, FSampleRate div 1000, 0, + IfThen(FNetwork.Device.BoardType in [3, 4, 5, 10], 1, 0), + FDitherEnabled, FRandomEnabled, FRX2SampleRate div 1000); + + FRX2SpectrumDirty := True; + FRX2WaterfallDirty := True; + if Assigned(PbRulerRX2) then PbRulerRX2.Invalidate; + if Assigned(PbSpectrumRX2) then PbSpectrumRX2.Invalidate; + if Assigned(PbWaterfallRX2) then PbWaterfallRX2.Invalidate; end; // =========================================================================== @@ -6902,6 +7774,27 @@ begin end; end; +procedure TMainForm.OnRX2SpectrumReady(const Pixels: array of Single; + Count: Integer); +begin + if not FRX2Enabled then Exit; + FSpecViewRX2.SetSpectrumData(Pixels, Count); + FRX2SpectrumDirty := True; +end; + +procedure TMainForm.OnRX2WaterfallReady(const Pixels: array of Single; + Count: Integer); +begin + if not FRX2Enabled then Exit; + Inc(FRX2WaterfallFrameCounter); + if FRX2WaterfallFrameCounter >= Max(1, FWaterfallFrameInterval) then + begin + FRX2WaterfallFrameCounter := 0; + FSpecViewRX2.SetWaterfallData(Pixels, Count); + FRX2WaterfallDirty := True; + end; +end; + // =========================================================================== // Settings apply methods (вызываются из TSettingsForm) // =========================================================================== @@ -6913,7 +7806,23 @@ begin FDSPEngine.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgTimeMS); if FWDSPReady then FDSPEngine.SetFFTParams(FFTSize, WinType); + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + begin + FDSPEngineRX2.SetSpectrumDisplay(SpecDet, SpecAvgMode, SpecAvgTimeMS); + if PbSpectrumRX2 <> nil then + FDSPEngineRX2.SetSpectrumWidth(PbSpectrumRX2.Width); + FDSPEngineRX2.SetFFTParams(FFTSize, WinType); + end; + FSpecView.ResetSpectrumBuf; + FSpecViewRX2.ResetSpectrumBuf; + FSpecView.InvalidateRulerCache; + FSpecViewRX2.InvalidateRulerCache; FSpectrumDirty := True; + FRX2SpectrumDirty := True; + if Assigned(PbSpectrum) then PbSpectrum.Invalidate; + if Assigned(PbSpectrumRX2) then PbSpectrumRX2.Invalidate; + if Assigned(PbRuler) then PbRuler.Invalidate; + if Assigned(PbRulerRX2) then PbRulerRX2.Invalidate; end; procedure TMainForm.ApplyWaterfallParams(WfDet, WfAvgMode: Integer; @@ -6921,6 +7830,8 @@ procedure TMainForm.ApplyWaterfallParams(WfDet, WfAvgMode: Integer; begin if FWDSPReady then FDSPEngine.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgTimeMS); + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + FDSPEngineRX2.SetWaterfallDisplay(WfDet, WfAvgMode, WfAvgTimeMS); FWfManualHigh := WfHigh; FWfManualLow := WfLow; FWfAGCOffset := WfAGCOffset; @@ -6928,7 +7839,15 @@ begin FSpecView.WfManualLow := FWfManualLow; FSpecView.WfAGCOffset := FWfAGCOffset; FSpecView.ResetWfAvgBuf; + FSpecViewRX2.WfManualHigh := FWfManualHigh; + FSpecViewRX2.WfManualLow := FWfManualLow; + FSpecViewRX2.WfAGCOffset := FWfAGCOffset; + FSpecViewRX2.ResetWfAvgBuf; FSpectrumDirty := True; + FWaterfallDirty := True; + FRX2WaterfallDirty := True; + if Assigned(PbWaterfall) then PbWaterfall.Invalidate; + if Assigned(PbWaterfallRX2) then PbWaterfallRX2.Invalidate; end; procedure TMainForm.ApplyGridParams(RefLevel, Range, GridStep: Double); @@ -6954,8 +7873,15 @@ begin FSpecView.SpecRange := FSpecRange; FSpecView.SpecGridStep := FSpecGridStep; end; + if Assigned(FSpecViewRX2) then + begin + FSpecViewRX2.SpecRefLevel := FSpecRefLevel; + FSpecViewRX2.SpecRange := FSpecRange; + FSpecViewRX2.SpecGridStep := FSpecGridStep; + end; InvalidateGridCache; FSpectrumDirty := True; + FRX2SpectrumDirty := True; end; procedure TMainForm.ApplyAudioDevice(DevIndex: Integer; const DevName: string); @@ -7035,10 +7961,16 @@ begin else FWaterfallFrameInterval := 2; FWaterfallFrameCounter := 0; + FRX2WaterfallFrameCounter := 0; FWaterfallDirty := True; + FRX2WaterfallDirty := True; FSpecView.WfFrameInterval := FWaterfallFrameInterval; + if Assigned(FSpecViewRX2) then + FSpecViewRX2.WfFrameInterval := FWaterfallFrameInterval; if FWDSPReady then FDSPEngine.SetDisplayFPS(FPS); + if Assigned(FDSPEngineRX2) and FDSPEngineRX2.Initialized then + FDSPEngineRX2.SetDisplayFPS(FPS); end; procedure TMainForm.ApplyFreqMhzDigits(Digits: Integer); diff --git a/SampleRateOverlay.pas b/SampleRateOverlay.pas index 0e10095..0cc02ba 100644 --- a/SampleRateOverlay.pas +++ b/SampleRateOverlay.pas @@ -26,6 +26,7 @@ type FOnSpanSelect: TSpanSelectEvent; FOnHidePanel: THidePanelEvent; FOnInvalidate: TNotifyEvent; + FFirstButtonCaption: string; FLeft: Integer; FTop: Integer; FWidth: Integer; @@ -54,6 +55,7 @@ type property Width: Integer read FWidth; property Height: Integer read FHeight; property HotIdx: Integer read FHotIdx; + property FirstButtonCaption: string read FFirstButtonCaption write FFirstButtonCaption; property OnSpanSelect: TSpanSelectEvent read FOnSpanSelect write FOnSpanSelect; property OnHidePanel: THidePanelEvent read FOnHidePanel write FOnHidePanel; property OnInvalidate: TNotifyEvent read FOnInvalidate write FOnInvalidate; @@ -200,7 +202,12 @@ begin C.Brush.Style := bsClear; C.Rectangle(HR); C.Font.Size := 8; - if FPanelHidden then Cap := '>' else Cap := '<'; + if FFirstButtonCaption <> '' then + Cap := FFirstButtonCaption + else if FPanelHidden then + Cap := '>' + else + Cap := '<'; TW := C.TextWidth(Cap); C.Font.Color := CLR_TEXT; C.Brush.Style := bsClear; diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 546e45e..51fd705 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -229,6 +229,11 @@ type // не меняется. FPostTXMuteUntil: QWord; FActiveDisplayID: Integer; // RX_DISP_ID или TX_DISP_ID + FRXChan: Integer; + FTXChan: Integer; + FRXDispID: Integer; + FTXDispID: Integer; + FTXEnabled: Boolean; FTXMicSource: TTXMicSource; FOnPullMic: TPullMicSamplesFunc; @@ -323,7 +328,12 @@ type public constructor Create(SampleRate: Integer = 192000; AudioRate: Integer = 48000; - BufSize: Integer = DSP_BUFSIZE); + BufSize: Integer = DSP_BUFSIZE; + RXChan: Integer = RXA_CHAN; + TXChan: Integer = TXA_CHAN; + RXDispID: Integer = RX_DISP_ID; + TXDispID: Integer = TX_DISP_ID; + EnableTX: Boolean = True); destructor Destroy; override; // Открыть DSP каналы @@ -479,7 +489,7 @@ type implementation // =========================================================================== -// TX DSP поток — читает mic ring buffer, вызывает fexchange0(TXA_CHAN), +// TX DSP поток — читает mic ring buffer, вызывает fexchange0(FTXChan), // сигнализирует о готовых IQ данных через FOnTXIQ callback. // Аналог tx_thread в piHPSDR/transmitter.c // =========================================================================== @@ -715,11 +725,17 @@ end; // Constructor / Destructor // --------------------------------------------------------------------------- -constructor TWDSPEngine.Create(SampleRate, AudioRate, BufSize: Integer); +constructor TWDSPEngine.Create(SampleRate, AudioRate, BufSize, RXChan, TXChan, + RXDispID, TXDispID: Integer; EnableTX: Boolean); begin inherited Create; FSampleRate := SampleRate; FAudioRate := AudioRate; + FRXChan := RXChan; + FTXChan := TXChan; + FRXDispID := RXDispID; + FTXDispID := TXDispID; + FTXEnabled := EnableTX; // BufSize = dsp_size @ AudioRate (внутренний DSP буфер, piHPSDR buffer_size = 1024) // FBufSize = in_size @ SampleRate = BufSize * (SampleRate/AudioRate) // piHPSDR: in_size = buffer_size * sample_rate/48000 = 1024*4 = 4096 @ 192kHz @@ -784,7 +800,7 @@ begin FTXActive := False; FKeepRXDuringTX := False; FPostTXMuteUntil := 0; - FActiveDisplayID := RX_DISP_ID; + FActiveDisplayID := FRXDispID; FTXMicSource := txmsRadio; FOnPullMic := nil; FTXThread := nil; @@ -865,19 +881,22 @@ begin if FAnalyzerOpen then Exit; Success := -1; - XCreateAnalyzer(RX_DISP_ID, @Success, 262144, 1, 1, nil); + XCreateAnalyzer(FRXDispID, @Success, 262144, 1, 1, nil); if Success <> 0 then begin FLastError := Format('XCreateAnalyzer(RX) failed: %d', [Success]); Exit; end; - // Отдельный analyzer для TX-спектра/водопада. Ошибку логируем в LastError, - // но не считаем фатальной — RX без TX-display продолжает работать. - Success := -1; - XCreateAnalyzer(TX_DISP_ID, @Success, 262144, 1, 1, nil); - if Success <> 0 then - FLastError := Format('XCreateAnalyzer(TX) failed: %d', [Success]); + if FTXEnabled then + begin + // Отдельный analyzer для TX-спектра/водопада. Ошибку логируем в LastError, + // но не считаем фатальной — RX без TX-display продолжает работать. + Success := -1; + XCreateAnalyzer(FTXDispID, @Success, 262144, 1, 1, nil); + if Success <> 0 then + FLastError := Format('XCreateAnalyzer(TX) failed: %d', [Success]); + end; FFlp[0] := 0; ApplyAnalyzerSettings; @@ -888,7 +907,7 @@ begin end; procedure TWDSPEngine.ApplyRXAnalyzerSettings; -// Применяет FFFTSize / FWindowType / FSpec* / FWf* к RX_DISP_ID. +// Применяет FFFTSize / FWindowType / FSpec* / FWf* к FRXDispID. var AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels: Integer; Backmult, WfBackmult: Double; @@ -905,24 +924,24 @@ begin AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult); AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WfAvgCount, WfBackmult); - SetAnalyzer(RX_DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, + SetAnalyzer(FRXDispID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, FWindowType, 14.0, Overlap, 0, 0.0, 0.0, DisplayPixels, 1, 0, 0.0, 0.0, MaxW); - SetDisplayDetectorMode(RX_DISP_ID, 0, FSpecDetector); - SetDisplayAverageMode (RX_DISP_ID, 0, FSpecAvgMode); - SetDisplayNumAverage (RX_DISP_ID, 0, AvgCount); - SetDisplayAvBackmult (RX_DISP_ID, 0, Backmult); - SetDisplayNormOneHz (RX_DISP_ID, 0, UsePanNormOneHz(FSpecDetector)); - SetDisplayDetectorMode(RX_DISP_ID, 1, FWfDetector); - SetDisplayAverageMode (RX_DISP_ID, 1, FWfAvgMode); - SetDisplayNumAverage (RX_DISP_ID, 1, WfAvgCount); - SetDisplayAvBackmult (RX_DISP_ID, 1, WfBackmult); - SetDisplayNormOneHz (RX_DISP_ID, 1, 0); - SetDisplaySampleRate (RX_DISP_ID, FSampleRate); + SetDisplayDetectorMode(FRXDispID, 0, FSpecDetector); + SetDisplayAverageMode (FRXDispID, 0, FSpecAvgMode); + SetDisplayNumAverage (FRXDispID, 0, AvgCount); + SetDisplayAvBackmult (FRXDispID, 0, Backmult); + SetDisplayNormOneHz (FRXDispID, 0, UsePanNormOneHz(FSpecDetector)); + SetDisplayDetectorMode(FRXDispID, 1, FWfDetector); + SetDisplayAverageMode (FRXDispID, 1, FWfAvgMode); + SetDisplayNumAverage (FRXDispID, 1, WfAvgCount); + SetDisplayAvBackmult (FRXDispID, 1, WfBackmult); + SetDisplayNormOneHz (FRXDispID, 1, 0); + SetDisplaySampleRate (FRXDispID, FSampleRate); end; procedure TWDSPEngine.ApplyTXAnalyzerSettings; -// Применяет FTXFFTSize / FTXWindowType / FTXSpec* / FTXWf* к TX_DISP_ID. +// Применяет FTXFFTSize / FTXWindowType / FTXSpec* / FTXWf* к FTXDispID. // FFT/Window/Det/Avg отдельные от RX — TX-спектр обычно нужен мельче по FFT // (TX-полоса узкая) и с другим усреднением (быстрее реагировать на голос). var @@ -931,6 +950,7 @@ var EffectiveFPS: Double; const KEEP_TIME_SEC = 0.10; begin + if not FTXEnabled then Exit; AnalyzerFFT := NormalizeFFTSize(FTXFFTSize); FTXDispPos := 0; MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FTXSampleRate, @@ -941,27 +961,28 @@ begin AverageTimeToParams(FTXSpecAvgMode, FTXSpecAvgTimeMS, AvgCount, Backmult); AverageTimeToParams(FTXWfAvgMode, FTXWfAvgTimeMS, WfAvgCount, WfBackmult); - SetAnalyzer(TX_DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, + SetAnalyzer(FTXDispID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, FTXWindowType, 14.0, Overlap, 0, 0.0, 0.0, DisplayPixels, 1, 0, 0.0, 0.0, MaxW); - SetDisplayDetectorMode(TX_DISP_ID, 0, FTXSpecDetector); - SetDisplayAverageMode (TX_DISP_ID, 0, FTXSpecAvgMode); - SetDisplayNumAverage (TX_DISP_ID, 0, AvgCount); - SetDisplayAvBackmult (TX_DISP_ID, 0, Backmult); - SetDisplayNormOneHz (TX_DISP_ID, 0, UsePanNormOneHz(FTXSpecDetector)); - SetDisplayDetectorMode(TX_DISP_ID, 1, FTXWfDetector); - SetDisplayAverageMode (TX_DISP_ID, 1, FTXWfAvgMode); - SetDisplayNumAverage (TX_DISP_ID, 1, WfAvgCount); - SetDisplayAvBackmult (TX_DISP_ID, 1, WfBackmult); - SetDisplayNormOneHz (TX_DISP_ID, 1, 0); - SetDisplaySampleRate (TX_DISP_ID, FTXSampleRate); + SetDisplayDetectorMode(FTXDispID, 0, FTXSpecDetector); + SetDisplayAverageMode (FTXDispID, 0, FTXSpecAvgMode); + SetDisplayNumAverage (FTXDispID, 0, AvgCount); + SetDisplayAvBackmult (FTXDispID, 0, Backmult); + SetDisplayNormOneHz (FTXDispID, 0, UsePanNormOneHz(FTXSpecDetector)); + SetDisplayDetectorMode(FTXDispID, 1, FTXWfDetector); + SetDisplayAverageMode (FTXDispID, 1, FTXWfAvgMode); + SetDisplayNumAverage (FTXDispID, 1, WfAvgCount); + SetDisplayAvBackmult (FTXDispID, 1, WfBackmult); + SetDisplayNormOneHz (FTXDispID, 1, 0); + SetDisplaySampleRate (FTXDispID, FTXSampleRate); end; procedure TWDSPEngine.ApplyAnalyzerSettings; // Совместимый вход: применяет настройки и к RX, и к TX analyzer'у. begin ApplyRXAnalyzerSettings; - ApplyTXAnalyzerSettings; + if FTXEnabled then + ApplyTXAnalyzerSettings; end; procedure TWDSPEngine.CloseAnalyzer; @@ -973,8 +994,9 @@ begin FDisplayThread.WaitFor; FreeAndNil(FDisplayThread); end; - DestroyAnalyzer(RX_DISP_ID); - DestroyAnalyzer(TX_DISP_ID); + DestroyAnalyzer(FRXDispID); + if FTXEnabled then + DestroyAnalyzer(FTXDispID); FAnalyzerOpen := False; end; @@ -1011,7 +1033,7 @@ begin end; OpenChannel( - RXA_CHAN, + FRXChan, FBufSize, FAudioBufSize, FSampleRate, @@ -1023,45 +1045,49 @@ begin 0 ); - create_anbEXT(RXA_CHAN, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); - create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); + create_anbEXT(FRXChan, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); + create_nobEXT(FRXChan, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20); - SetRXABandpassWindow(RXA_CHAN, 1); - SetRXAMode(RXA_CHAN, ModeToWDSP(FMode)); - SetRXAShiftRun(RXA_CHAN, 1); - SetRXAShiftFreq(RXA_CHAN, 0.0); - RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh); + SetRXABandpassWindow(FRXChan, 1); + SetRXAMode(FRXChan, ModeToWDSP(FMode)); + SetRXAShiftRun(FRXChan, 1); + SetRXAShiftFreq(FRXChan, 0.0); + RXASetPassband(FRXChan, FFilterLow, FFilterHigh); SetAGC(FAGCMode, 50.0); - SetRXAPanelGain1(RXA_CHAN, FVolume); - SetRXAPanelSelect(RXA_CHAN, 3); - SetRXAPanelRun(RXA_CHAN, 1); - SetChannelState(RXA_CHAN, 1, 0); + SetRXAPanelGain1(FRXChan, FVolume); + SetRXAPanelSelect(FRXChan, 3); + SetRXAPanelRun(FRXChan, 1); + SetChannelState(FRXChan, 1, 0); - // 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, - FTXSampleRate, - 1, - 0, - 0.010, 0.010, 0.010, 0.010, - 0 - ); + if FTXEnabled then + begin + // TXA: mic @ FAudioRate → IQ @ FTXSampleRate (фиксированный DUC rate, не зависит от RX). + // out_size = FAudioBufSize * FTXSampleRate/FAudioRate = FTXOutBufSize (4096 @ 192k tx). + // FTXOut уже имеет размер FTXOutBufSize*2. + OpenChannel( + FTXChan, + FAudioBufSize, + FAudioBufSize, + FAudioRate, + FAudioRate, + FTXSampleRate, + 1, + 0, + 0.010, 0.010, 0.010, 0.010, + 0 + ); - SetTXAMode(TXA_CHAN, ModeToWDSP(FMode)); - SetTXAPanelRun(TXA_CHAN, 1); - // PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I. - SetTXAPanelSelect(TXA_CHAN, 2); + SetTXAMode(FTXChan, ModeToWDSP(FMode)); + SetTXAPanelRun(FTXChan, 1); + // PatchPanel: 1=Q, 2=I. Mic подаётся в I (FTXIn[i*2]), Q=0 — выбираем I. + SetTXAPanelSelect(FTXChan, 2); + end; FInitialized := True; // Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ, // Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS. - ApplyTXChainSettings; + if FTXEnabled then + ApplyTXChainSettings; ApplyNoiseFilterState; Result := True; @@ -1094,10 +1120,11 @@ begin end; CloseAnalyzer; - destroy_anbEXT(RXA_CHAN); - destroy_nobEXT(RXA_CHAN); - CloseChannel(RXA_CHAN); - CloseChannel(TXA_CHAN); + destroy_anbEXT(FRXChan); + destroy_nobEXT(FRXChan); + CloseChannel(FRXChan); + if FTXEnabled then + CloseChannel(FTXChan); FInitialized := False; end; @@ -1179,7 +1206,7 @@ procedure TWDSPEngine.PushIQItemToDSP(const Item: TIQQueueItem); // Вызывается из DSP потока — декодирует IQ и накапливает до FBufSize. // В non-DUP TX (FTXActive and not FKeepRXDuringTX) полностью пропускаем // IQ-пакет: не кормим RX-анализатор и не накапливаем для fexchange0. -// Иначе TX leakage из DDC оседает в RXA pipeline / RX_DISP_ID FFT-истории +// Иначе TX leakage из DDC оседает в RXA pipeline / FRXDispID FFT-истории // и звучит/виден на водопаде первые секунды после возврата на RX. var i, Pos: Integer; @@ -1231,7 +1258,7 @@ begin if FDispPos >= DISPLAY_BLOCK_SIZE then begin - Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]); + Spectrum0(1, FRXDispID, 0, 0, @FDispBuf[0]); FDispPos := 0; end; end; @@ -1246,7 +1273,7 @@ begin if FTXDispPos >= DISPLAY_BLOCK_SIZE then begin - Spectrum0(1, TX_DISP_ID, 0, 0, @FTXDispBuf[0]); + Spectrum0(1, FTXDispID, 0, 0, @FTXDispBuf[0]); FTXDispPos := 0; end; end; @@ -1270,12 +1297,12 @@ begin // DSP обработка Err := 0; - fexchange0(RXA_CHAN, @FRXIn[0], @FRXOut[0], @Err); + fexchange0(FRXChan, @FRXIn[0], @FRXOut[0], @Err); // S-meter: читаем ОДИН раз в DSP-колбэке и кешируем в FSMeter. // GetRXAMeter сбрасывает аккумулятор после вызова, поэтому второй // вызов вернёт 0/-∞ — отсюда "падение до нуля". Только здесь! - FSMeter := GetRXAMeter(RXA_CHAN, RXA_S_AV); + FSMeter := GetRXAMeter(FRXChan, RXA_S_AV); // Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1). // При TX RX-аудио глушим (как в Thetis/piHPSDR при не-duplex MOX), @@ -1309,8 +1336,9 @@ procedure TWDSPEngine.SetMode(Mode: Integer); begin FMode := Mode; if not FInitialized then Exit; - SetRXAMode(RXA_CHAN, ModeToWDSP(Mode)); - SetTXAMode(TXA_CHAN, ModeToWDSP(Mode)); + SetRXAMode(FRXChan, ModeToWDSP(Mode)); + if FTXEnabled then + SetTXAMode(FTXChan, ModeToWDSP(Mode)); // НЕ вызываем ApplyDefaultFilter — фильтр устанавливается явно из UI end; @@ -1320,7 +1348,7 @@ begin FFilterHigh := High; if not FInitialized then Exit; // RXASetPassband — правильный unified API, пересчитывает фильтр целиком - RXASetPassband(RXA_CHAN, Low, High); + RXASetPassband(FRXChan, Low, High); end; procedure TWDSPEngine.SetAGC(Mode: TWDSPAGCMode; FixedGain: Double); @@ -1329,40 +1357,40 @@ begin if not FInitialized then Exit; // Точно по piHPSDR receiver.c: set_agc() - SetRXAAGCMode(RXA_CHAN, Ord(Mode)); - SetRXAAGCSlope(RXA_CHAN, FAGCSlope); - SetRXAAGCTop(RXA_CHAN, FAGCTop); + SetRXAAGCMode(FRXChan, Ord(Mode)); + SetRXAAGCSlope(FRXChan, FAGCSlope); + SetRXAAGCTop(FRXChan, FAGCTop); case Mode of agcOff: - SetRXAAGCFixed(RXA_CHAN, FixedGain); + SetRXAAGCFixed(FRXChan, FixedGain); agcLong: begin - SetRXAAGCAttack(RXA_CHAN, 2); - SetRXAAGCHang(RXA_CHAN, 2000); - SetRXAAGCDecay(RXA_CHAN, 2000); - SetRXAAGCHangThreshold(RXA_CHAN, FAGCHangThreshold); + SetRXAAGCAttack(FRXChan, 2); + SetRXAAGCHang(FRXChan, 2000); + SetRXAAGCDecay(FRXChan, 2000); + SetRXAAGCHangThreshold(FRXChan, FAGCHangThreshold); end; agcSlow: begin - SetRXAAGCAttack(RXA_CHAN, 2); - SetRXAAGCHang(RXA_CHAN, 1000); - SetRXAAGCDecay(RXA_CHAN, 500); - SetRXAAGCHangThreshold(RXA_CHAN, FAGCHangThreshold); + SetRXAAGCAttack(FRXChan, 2); + SetRXAAGCHang(FRXChan, 1000); + SetRXAAGCDecay(FRXChan, 500); + SetRXAAGCHangThreshold(FRXChan, FAGCHangThreshold); end; agcMedium: begin - SetRXAAGCAttack(RXA_CHAN, 2); - SetRXAAGCHang(RXA_CHAN, 0); - SetRXAAGCDecay(RXA_CHAN, 250); - SetRXAAGCHangThreshold(RXA_CHAN, 100); + SetRXAAGCAttack(FRXChan, 2); + SetRXAAGCHang(FRXChan, 0); + SetRXAAGCDecay(FRXChan, 250); + SetRXAAGCHangThreshold(FRXChan, 100); end; agcFast: begin - SetRXAAGCAttack(RXA_CHAN, 2); - SetRXAAGCHang(RXA_CHAN, 0); - SetRXAAGCDecay(RXA_CHAN, 50); - SetRXAAGCHangThreshold(RXA_CHAN, 100); + SetRXAAGCAttack(FRXChan, 2); + SetRXAAGCHang(FRXChan, 0); + SetRXAAGCDecay(FRXChan, 50); + SetRXAAGCHangThreshold(FRXChan, 100); end; end; @@ -1374,14 +1402,14 @@ procedure TWDSPEngine.SetShift(ShiftHz: Double); begin FShiftHz := ShiftHz; if not FInitialized then Exit; - SetRXAShiftFreq(RXA_CHAN, ShiftHz); + SetRXAShiftFreq(FRXChan, ShiftHz); end; procedure TWDSPEngine.SetAGCTop(TopDBm: Double); begin FAGCTop := TopDBm; if not FInitialized then Exit; - SetRXAAGCTop(RXA_CHAN, TopDBm); + SetRXAAGCTop(FRXChan, TopDBm); UpdateAGCLines(FLastSpectrumW); end; @@ -1389,14 +1417,14 @@ procedure TWDSPEngine.SetAGCSlope(Slope: Integer); begin FAGCSlope := Slope; if not FInitialized then Exit; - SetRXAAGCSlope(RXA_CHAN, Slope); + SetRXAAGCSlope(FRXChan, Slope); end; procedure TWDSPEngine.SetAGCHangThreshold(Threshold: Integer); begin FAGCHangThreshold := Threshold; if not FInitialized then Exit; - SetRXAAGCHangThreshold(RXA_CHAN, Threshold); + SetRXAAGCHangThreshold(FRXChan, Threshold); UpdateAGCLines(FLastSpectrumW); end; @@ -1413,8 +1441,8 @@ procedure TWDSPEngine.UpdateAGCLines(SpectrumW: Integer); begin if not FInitialized then Exit; if SpectrumW <= 0 then Exit; // окно ещё не показано — не читаем - GetRXAAGCHangLevel(RXA_CHAN, @FAGCHangLevel); - GetRXAAGCThresh(RXA_CHAN, @FAGCThresh, + GetRXAAGCHangLevel(FRXChan, @FAGCHangLevel); + GetRXAAGCThresh(FRXChan, @FAGCThresh, SpectrumW, // реальная ширина дисплея в пикселях FSampleRate); end; @@ -1423,65 +1451,65 @@ procedure TWDSPEngine.ApplyNRState; begin if not FInitialized then Exit; - SetRXAANRVals(RXA_CHAN, 64, 16, 16e-4, 10e-7); - SetRXAANRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); - SetRXAANRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_1)); + SetRXAANRVals(FRXChan, 64, 16, 16e-4, 10e-7); + SetRXAANRPosition(FRXChan, FILTER_NR_AGC_POSITION); + SetRXAANRRun(FRXChan, Ord(FNRMode = NR_MODE_1)); - SetRXAEMNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); - SetRXAEMNRgainMethod(RXA_CHAN, FILTER_NR2_GAIN_METHOD); - SetRXAEMNRnpeMethod(RXA_CHAN, FILTER_NR2_NPE_METHOD); - SetRXAEMNRtrainZetaThresh(RXA_CHAN, FILTER_NR2_TRAINED_THRESHOLD); - SetRXAEMNRtrainT2(RXA_CHAN, FILTER_NR2_TRAINED_T2); - SetRXAEMNRpost2Taper(RXA_CHAN, FILTER_NR2_POST_TAPER); - SetRXAEMNRpost2Nlevel(RXA_CHAN, FILTER_NR2_POST_NLEVEL); - SetRXAEMNRpost2Factor(RXA_CHAN, FILTER_NR2_POST_FACTOR); - SetRXAEMNRpost2Rate(RXA_CHAN, FILTER_NR2_POST_RATE); - SetRXAEMNRaeRun(RXA_CHAN, 1); - SetRXAEMNRpost2Run(RXA_CHAN, FILTER_NR2_POST_RUN); - SetRXAEMNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_2)); + SetRXAEMNRPosition(FRXChan, FILTER_NR_AGC_POSITION); + SetRXAEMNRgainMethod(FRXChan, FILTER_NR2_GAIN_METHOD); + SetRXAEMNRnpeMethod(FRXChan, FILTER_NR2_NPE_METHOD); + SetRXAEMNRtrainZetaThresh(FRXChan, FILTER_NR2_TRAINED_THRESHOLD); + SetRXAEMNRtrainT2(FRXChan, FILTER_NR2_TRAINED_T2); + SetRXAEMNRpost2Taper(FRXChan, FILTER_NR2_POST_TAPER); + SetRXAEMNRpost2Nlevel(FRXChan, FILTER_NR2_POST_NLEVEL); + SetRXAEMNRpost2Factor(FRXChan, FILTER_NR2_POST_FACTOR); + SetRXAEMNRpost2Rate(FRXChan, FILTER_NR2_POST_RATE); + SetRXAEMNRaeRun(FRXChan, 1); + SetRXAEMNRpost2Run(FRXChan, FILTER_NR2_POST_RUN); + SetRXAEMNRRun(FRXChan, Ord(FNRMode = NR_MODE_2)); - SetRXARNNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); - SetRXARNNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_3)); + SetRXARNNRPosition(FRXChan, FILTER_NR_AGC_POSITION); + SetRXARNNRRun(FRXChan, Ord(FNRMode = NR_MODE_3)); - SetRXASBNRreductionAmount(RXA_CHAN, FILTER_NR4_REDUCTION_AMOUNT); - SetRXASBNRsmoothingFactor(RXA_CHAN, FILTER_NR4_SMOOTHING_FACTOR); - SetRXASBNRwhiteningFactor(RXA_CHAN, FILTER_NR4_WHITENING_FACTOR); - SetRXASBNRnoiseRescale(RXA_CHAN, FILTER_NR4_NOISE_RESCALE); - SetRXASBNRpostFilterThreshold(RXA_CHAN, FILTER_NR4_POST_THRESHOLD); - SetRXASBNRnoiseScalingType(RXA_CHAN, FILTER_NR4_NOISE_SCALING_TYPE); - SetRXASBNRPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); - SetRXASBNRRun(RXA_CHAN, Ord(FNRMode = NR_MODE_4)); + SetRXASBNRreductionAmount(FRXChan, FILTER_NR4_REDUCTION_AMOUNT); + SetRXASBNRsmoothingFactor(FRXChan, FILTER_NR4_SMOOTHING_FACTOR); + SetRXASBNRwhiteningFactor(FRXChan, FILTER_NR4_WHITENING_FACTOR); + SetRXASBNRnoiseRescale(FRXChan, FILTER_NR4_NOISE_RESCALE); + SetRXASBNRpostFilterThreshold(FRXChan, FILTER_NR4_POST_THRESHOLD); + SetRXASBNRnoiseScalingType(FRXChan, FILTER_NR4_NOISE_SCALING_TYPE); + SetRXASBNRPosition(FRXChan, FILTER_NR_AGC_POSITION); + SetRXASBNRRun(FRXChan, Ord(FNRMode = NR_MODE_4)); end; procedure TWDSPEngine.ApplyNBState; begin if not FInitialized then Exit; - SetEXTANBTau(RXA_CHAN, FILTER_NB_TAU); - SetEXTANBHangtime(RXA_CHAN, FILTER_NB_HANGTIME); - SetEXTANBAdvtime(RXA_CHAN, FILTER_NB_ADVTIME); - SetEXTANBThreshold(RXA_CHAN, FILTER_NB_THRESHOLD); - SetEXTANBRun(RXA_CHAN, Ord(FNBMode = NB_MODE_1)); + SetEXTANBTau(FRXChan, FILTER_NB_TAU); + SetEXTANBHangtime(FRXChan, FILTER_NB_HANGTIME); + SetEXTANBAdvtime(FRXChan, FILTER_NB_ADVTIME); + SetEXTANBThreshold(FRXChan, FILTER_NB_THRESHOLD); + SetEXTANBRun(FRXChan, Ord(FNBMode = NB_MODE_1)); - SetEXTNOBMode(RXA_CHAN, FILTER_NB2_MODE); - SetEXTNOBTau(RXA_CHAN, FILTER_NB_TAU); - SetEXTNOBHangtime(RXA_CHAN, FILTER_NB_HANGTIME); - SetEXTNOBAdvtime(RXA_CHAN, FILTER_NB_ADVTIME); - SetEXTNOBThreshold(RXA_CHAN, FILTER_NB_THRESHOLD); - SetEXTNOBRun(RXA_CHAN, Ord(FNBMode = NB_MODE_2)); + SetEXTNOBMode(FRXChan, FILTER_NB2_MODE); + SetEXTNOBTau(FRXChan, FILTER_NB_TAU); + SetEXTNOBHangtime(FRXChan, FILTER_NB_HANGTIME); + SetEXTNOBAdvtime(FRXChan, FILTER_NB_ADVTIME); + SetEXTNOBThreshold(FRXChan, FILTER_NB_THRESHOLD); + SetEXTNOBRun(FRXChan, Ord(FNBMode = NB_MODE_2)); end; procedure TWDSPEngine.ApplySNBState; begin if not FInitialized then Exit; - SetRXASNBARun(RXA_CHAN, Ord(FSNBEnabled)); + SetRXASNBARun(FRXChan, Ord(FSNBEnabled)); end; procedure TWDSPEngine.ApplyANFState; begin if not FInitialized then Exit; - SetRXAANFPosition(RXA_CHAN, FILTER_NR_AGC_POSITION); - SetRXAANFRun(RXA_CHAN, Ord(FANFEnabled)); + SetRXAANFPosition(FRXChan, FILTER_NR_AGC_POSITION); + SetRXAANFRun(FRXChan, Ord(FANFEnabled)); end; procedure TWDSPEngine.ApplyNoiseFilterState; @@ -1535,7 +1563,7 @@ end; procedure TWDSPEngine.SetRXFMDeviation(Deviation: Double); begin if not FInitialized then Exit; - SetRXAFMDeviation(RXA_CHAN, Deviation); + SetRXAFMDeviation(FRXChan, Deviation); end; procedure TWDSPEngine.SetFMSquelch(Enable: Boolean; Level: Integer); @@ -1545,25 +1573,25 @@ begin if Enable then begin Threshold := Level / 200.0; // 0..100 → 0.0..0.5 - SetRXAFMSQThreshold(RXA_CHAN, Threshold); - SetRXAFMSQRun(RXA_CHAN, 1); + SetRXAFMSQThreshold(FRXChan, Threshold); + SetRXAFMSQRun(FRXChan, 1); end else - SetRXAFMSQRun(RXA_CHAN, 0); + SetRXAFMSQRun(FRXChan, 0); end; procedure TWDSPEngine.SetVolume(Vol: Double); begin FVolume := Max(0.0, Min(1.0, Vol)); if not FInitialized then Exit; - SetRXAPanelGain1(RXA_CHAN, FVolume); + SetRXAPanelGain1(FRXChan, FVolume); end; procedure TWDSPEngine.SetMute(Mute: Boolean); begin FMuted := Mute; if not FInitialized then Exit; - SetRXAPanelRun(RXA_CHAN, Ord(not Mute)); + SetRXAPanelRun(FRXChan, Ord(not Mute)); end; // --------------------------------------------------------------------------- @@ -1572,43 +1600,53 @@ end; procedure TWDSPEngine.SetTXMode(Mode: Integer); begin + if not FTXEnabled then Exit; if not FInitialized then Exit; - SetTXAMode(TXA_CHAN, ModeToWDSP(Mode)); + SetTXAMode(FTXChan, ModeToWDSP(Mode)); end; procedure TWDSPEngine.SetTXFilter(Low, High: Integer); begin + if not FTXEnabled then Exit; if not FInitialized then Exit; - SetTXABandpassFreqs(TXA_CHAN, Low, High); + SetTXABandpassFreqs(FTXChan, Low, High); end; procedure TWDSPEngine.SetDriveLevel(Level: Double); begin + if not FTXEnabled then Exit; if not FInitialized then Exit; - SetTXAALCMaxGain(TXA_CHAN, Max(0.0, Min(1.0, Level))); + SetTXAALCMaxGain(FTXChan, Max(0.0, Min(1.0, Level))); end; procedure TWDSPEngine.SetMicGain(GainDB: Double); begin FTXMicGainDB := GainDB; + if not FTXEnabled then Exit; if not FInitialized then Exit; - SetTXAPanelGain1(TXA_CHAN, Power(10.0, GainDB / 20.0)); + SetTXAPanelGain1(FTXChan, Power(10.0, GainDB / 20.0)); end; procedure TWDSPEngine.SetTXRun(Run: Boolean); begin + if not FTXEnabled then + begin + FTXActive := False; + FActiveDisplayID := FRXDispID; + Exit; + end; FTXActive := Run; FDispPos := 0; FTXDispPos := 0; if Run then - FActiveDisplayID := TX_DISP_ID + FActiveDisplayID := FTXDispID else - FActiveDisplayID := RX_DISP_ID; + FActiveDisplayID := FRXDispID; if Run then begin if FInitialized then - SetChannelState(TXA_CHAN, 1, 0); // run, no delay + SetChannelState(FTXChan, 1, 0); // run, no delay // Сброс mic ring buffer и запуск TX потока FTXMicHead := 0; FTXMicTail := 0; @@ -1619,7 +1657,7 @@ begin end; end else begin if FInitialized then - SetChannelState(TXA_CHAN, 0, 1); // stop, with slew + SetChannelState(FTXChan, 0, 1); // stop, with slew // Останавливаем TX поток if Assigned(FTXThread) then begin @@ -1647,12 +1685,12 @@ begin SignedFreq := -Abs(FreqHz) else SignedFreq := Abs(FreqHz); - SetTXAPostGenMode(TXA_CHAN, 0); // 0 = tone - SetTXAPostGenToneFreq(TXA_CHAN, SignedFreq); - SetTXAPostGenToneMag(TXA_CHAN, Max(0.0, Min(1.0, Mag))); - SetTXAPostGenRun(TXA_CHAN, 1); + SetTXAPostGenMode(FTXChan, 0); // 0 = tone + SetTXAPostGenToneFreq(FTXChan, SignedFreq); + SetTXAPostGenToneMag(FTXChan, Max(0.0, Min(1.0, Mag))); + SetTXAPostGenRun(FTXChan, 1); end else - SetTXAPostGenRun(TXA_CHAN, 0); + SetTXAPostGenRun(FTXChan, 0); end; procedure TWDSPEngine.SetDisplaySourceTX(UseTX: Boolean); @@ -1664,9 +1702,9 @@ begin FDispPos := 0; FTXDispPos := 0; if UseTX then - FActiveDisplayID := TX_DISP_ID + FActiveDisplayID := FTXDispID else - FActiveDisplayID := RX_DISP_ID; + FActiveDisplayID := FRXDispID; end; procedure TWDSPEngine.ProcessTXBlock; @@ -1702,9 +1740,9 @@ begin end; Err := 0; - fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err); + fexchange0(FTXChan, @FTXIn[0], @FTXOut[0], @Err); - // TX сигнал → отдельный TX-analyzer (TX_DISP_ID). + // TX сигнал → отдельный TX-analyzer (FTXDispID). // Подаём чанками по DISPLAY_BLOCK_SIZE — analyzer ожидает именно такой // размер блока (см. SetAnalyzer). Если подавать FTXOut целиком // (FTXOutBufSize пар), analyzer прочитает только первые DISPLAY_BLOCK_SIZE @@ -1795,35 +1833,35 @@ begin // Bandpass if (FTXFilterLow > 0) and (FTXFilterHigh > FTXFilterLow) then - SetTXABandpassFreqs(TXA_CHAN, FTXFilterLow, FTXFilterHigh); + SetTXABandpassFreqs(FTXChan, FTXFilterLow, FTXFilterHigh); if FTXFilterWindow >= 0 then - SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow); + SetTXABandpassWindow(FTXChan, FTXFilterWindow); if FTXFilterNC > 0 then - TXASetNC(TXA_CHAN, FTXFilterNC); + TXASetNC(FTXChan, FTXFilterNC); if FTXFilterMP >= 0 then - TXASetMP(TXA_CHAN, FTXFilterMP); + TXASetMP(FTXChan, FTXFilterMP); // Mic gain (panel gain1) - SetTXAPanelGain1(TXA_CHAN, Power(10.0, FTXMicGainDB / 20.0)); + SetTXAPanelGain1(FTXChan, Power(10.0, FTXMicGainDB / 20.0)); // Compressor - SetTXACompressorRun(TXA_CHAN, Ord(FTXCompressorOn)); - SetTXACompressorGain(TXA_CHAN, FTXCompressorGain); + SetTXACompressorRun(FTXChan, Ord(FTXCompressorOn)); + SetTXACompressorGain(FTXChan, FTXCompressorGain); // Leveler - SetTXALevelerSt(TXA_CHAN, Ord(FTXLevelerOn)); - SetTXALevelerTop(TXA_CHAN, FTXLevelerTop); - SetTXALevelerDecay(TXA_CHAN, FTXLevelerDecay); + SetTXALevelerSt(FTXChan, Ord(FTXLevelerOn)); + SetTXALevelerTop(FTXChan, FTXLevelerTop); + SetTXALevelerDecay(FTXChan, FTXLevelerDecay); // ALC - SetTXAALCSt(TXA_CHAN, Ord(FTXALCOn)); - SetTXAALCMaxGain(TXA_CHAN, FTXALCMaxGain); - SetTXAALCDecay(TXA_CHAN, FTXALCDecay); + SetTXAALCSt(FTXChan, Ord(FTXALCOn)); + SetTXAALCMaxGain(FTXChan, FTXALCMaxGain); + SetTXAALCDecay(FTXChan, FTXALCDecay); // Phase Rotator - SetTXAPHROTRun(TXA_CHAN, Ord(FTXPhaseRotOn)); - SetTXAPHROTNstages(TXA_CHAN, FTXPhaseRotStages); - SetTXAPHROTCorner(TXA_CHAN, FTXPhaseRotFreq); + SetTXAPHROTRun(FTXChan, Ord(FTXPhaseRotOn)); + SetTXAPHROTNstages(FTXChan, FTXPhaseRotStages); + SetTXAPHROTCorner(FTXChan, FTXPhaseRotFreq); // EQ TX (10-band: [0]=preamp, [1..10]=bands) for i := 0 to 10 do @@ -1832,21 +1870,21 @@ begin Freqs[i] := FTXEQFreqs[i]; Qs[i] := 0.707; // умолчание Q end; - SetTXAEQProfile(TXA_CHAN, FTXEQNumBands, @Freqs[0], @Gains[0]); - SetTXAEQRun(TXA_CHAN, Ord(FTXEQOn)); + SetTXAEQProfile(FTXChan, FTXEQNumBands, @Freqs[0], @Gains[0]); + SetTXAEQRun(FTXChan, Ord(FTXEQOn)); // AM - SetTXAAMCarrierLevel(TXA_CHAN, FTXAMCarrier); + SetTXAAMCarrierLevel(FTXChan, FTXAMCarrier); // FM - SetTXAFMDeviation(TXA_CHAN, FTXFMDeviation); + SetTXAFMDeviation(FTXChan, FTXFMDeviation); if (FTXFMLowCut < FTXFMHighCut) then - SetTXAFMAFFilter(TXA_CHAN, FTXFMLowCut, FTXFMHighCut); - SetTXAFMEmphPosition(TXA_CHAN, FTXFMEmphPos); + SetTXAFMAFFilter(FTXChan, FTXFMLowCut, FTXFMHighCut); + SetTXAFMEmphPosition(FTXChan, FTXFMEmphPos); // CTCSS - SetTXACTCSSFreq(TXA_CHAN, FTXCTCSSFreq); - SetTXACTCSSRun(TXA_CHAN, Ord(FTXCTCSSOn)); + SetTXACTCSSFreq(FTXChan, FTXCTCSSFreq); + SetTXACTCSSRun(FTXChan, Ord(FTXCTCSSOn)); end; procedure TWDSPEngine.SetTXFilterFull(Low, High, NC, MP, Window: Integer); @@ -1859,10 +1897,10 @@ begin FFilterLow := Low; FFilterHigh := High; if not FInitialized then Exit; - SetTXABandpassFreqs(TXA_CHAN, Low, High); - SetTXABandpassWindow(TXA_CHAN, Window); - if NC > 0 then TXASetNC(TXA_CHAN, NC); - if MP >= 0 then TXASetMP(TXA_CHAN, MP); + SetTXABandpassFreqs(FTXChan, Low, High); + SetTXABandpassWindow(FTXChan, Window); + if NC > 0 then TXASetNC(FTXChan, NC); + if MP >= 0 then TXASetMP(FTXChan, MP); end; procedure TWDSPEngine.SetTXCompressor(Enable: Boolean; GainDB: Double); @@ -1870,8 +1908,8 @@ begin FTXCompressorOn := Enable; FTXCompressorGain := GainDB; if not FInitialized then Exit; - SetTXACompressorGain(TXA_CHAN, GainDB); - SetTXACompressorRun(TXA_CHAN, Ord(Enable)); + SetTXACompressorGain(FTXChan, GainDB); + SetTXACompressorRun(FTXChan, Ord(Enable)); end; procedure TWDSPEngine.SetTXLeveler(Enable: Boolean; TopDB: Double; DecayMS: Integer); @@ -1880,9 +1918,9 @@ begin FTXLevelerTop := TopDB; FTXLevelerDecay := DecayMS; if not FInitialized then Exit; - SetTXALevelerTop(TXA_CHAN, TopDB); - SetTXALevelerDecay(TXA_CHAN, DecayMS); - SetTXALevelerSt(TXA_CHAN, Ord(Enable)); + SetTXALevelerTop(FTXChan, TopDB); + SetTXALevelerDecay(FTXChan, DecayMS); + SetTXALevelerSt(FTXChan, Ord(Enable)); end; procedure TWDSPEngine.SetTXALC(Enable: Boolean; MaxGainDB: Double; DecayMS: Integer); @@ -1891,9 +1929,9 @@ begin FTXALCMaxGain := MaxGainDB; FTXALCDecay := DecayMS; if not FInitialized then Exit; - SetTXAALCMaxGain(TXA_CHAN, MaxGainDB); - SetTXAALCDecay(TXA_CHAN, DecayMS); - SetTXAALCSt(TXA_CHAN, Ord(Enable)); + SetTXAALCMaxGain(FTXChan, MaxGainDB); + SetTXAALCDecay(FTXChan, DecayMS); + SetTXAALCSt(FTXChan, Ord(Enable)); end; procedure TWDSPEngine.SetTXPhaseRot(Enable: Boolean; Stages: Integer; FreqHz: Double); @@ -1902,9 +1940,9 @@ begin FTXPhaseRotStages := Stages; FTXPhaseRotFreq := FreqHz; if not FInitialized then Exit; - SetTXAPHROTNstages(TXA_CHAN, Stages); - SetTXAPHROTCorner(TXA_CHAN, FreqHz); - SetTXAPHROTRun(TXA_CHAN, Ord(Enable)); + SetTXAPHROTNstages(FTXChan, Stages); + SetTXAPHROTCorner(FTXChan, FreqHz); + SetTXAPHROTRun(FTXChan, Ord(Enable)); end; procedure TWDSPEngine.SetTXEQ(Enable: Boolean; NumBands: Integer; @@ -1924,15 +1962,15 @@ begin F[i] := Freqs[i]; end; if not FInitialized then Exit; - SetTXAEQProfile(TXA_CHAN, NumBands, @F[0], @G[0]); - SetTXAEQRun(TXA_CHAN, Ord(Enable)); + SetTXAEQProfile(FTXChan, NumBands, @F[0], @G[0]); + SetTXAEQRun(FTXChan, Ord(Enable)); end; procedure TWDSPEngine.SetTXAMCarrierLevel(Level: Double); begin FTXAMCarrier := Level; if not FInitialized then Exit; - WDSP.SetTXAAMCarrierLevel(TXA_CHAN, Level); + WDSP.SetTXAAMCarrierLevel(FTXChan, Level); end; procedure TWDSPEngine.SetTXFMParams(Deviation: Double; LowCut, HighCut, EmphPos: Integer); @@ -1942,10 +1980,10 @@ begin FTXFMHighCut := HighCut; FTXFMEmphPos := EmphPos; if not FInitialized then Exit; - SetTXAFMDeviation(TXA_CHAN, Deviation); + SetTXAFMDeviation(FTXChan, Deviation); if LowCut < HighCut then - SetTXAFMAFFilter(TXA_CHAN, LowCut, HighCut); - SetTXAFMEmphPosition(TXA_CHAN, EmphPos); + SetTXAFMAFFilter(FTXChan, LowCut, HighCut); + SetTXAFMEmphPosition(FTXChan, EmphPos); end; procedure TWDSPEngine.SetTXCTCSS(Enable: Boolean; FreqHz: Double); @@ -1953,8 +1991,8 @@ begin FTXCTCSSOn := Enable; FTXCTCSSFreq := FreqHz; if not FInitialized then Exit; - SetTXACTCSSFreq(TXA_CHAN, FreqHz); - SetTXACTCSSRun(TXA_CHAN, Ord(Enable)); + SetTXACTCSSFreq(FTXChan, FreqHz); + SetTXACTCSSRun(FTXChan, Ord(Enable)); end; function TWDSPEngine.TXMicRingAvail: Integer; @@ -2016,7 +2054,7 @@ var begin if not FInitialized or not FAnalyzerOpen then Exit; N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS); - DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive + DispID := FActiveDisplayID; // FRXDispID или FTXDispID, зависит от FTXActive Flag := 0; GetPixels(DispID, 0, @PixBuf[0], @Flag); @@ -2176,11 +2214,11 @@ begin FSpecAvgTimeMS := AvgTimeMS; if not FAnalyzerOpen then Exit; AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult); - SetDisplayDetectorMode(RX_DISP_ID, 0, FSpecDetector); - SetDisplayAverageMode(RX_DISP_ID, 0, FSpecAvgMode); - SetDisplayNumAverage(RX_DISP_ID, 0, AvgCount); - SetDisplayAvBackmult(RX_DISP_ID, 0, Backmult); - SetDisplayNormOneHz(RX_DISP_ID, 0, UsePanNormOneHz(FSpecDetector)); + SetDisplayDetectorMode(FRXDispID, 0, FSpecDetector); + SetDisplayAverageMode(FRXDispID, 0, FSpecAvgMode); + SetDisplayNumAverage(FRXDispID, 0, AvgCount); + SetDisplayAvBackmult(FRXDispID, 0, Backmult); + SetDisplayNormOneHz(FRXDispID, 0, UsePanNormOneHz(FSpecDetector)); end; procedure TWDSPEngine.SetWaterfallDisplay(Detector, AvgMode: Integer; @@ -2200,11 +2238,11 @@ begin FWfAvgTimeMS := AvgTimeMS; if not FAnalyzerOpen then Exit; AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, AvgCount, Backmult); - SetDisplayDetectorMode(RX_DISP_ID, 1, FWfDetector); - SetDisplayAverageMode(RX_DISP_ID, 1, FWfAvgMode); - SetDisplayNumAverage(RX_DISP_ID, 1, AvgCount); - SetDisplayAvBackmult(RX_DISP_ID, 1, Backmult); - SetDisplayNormOneHz(RX_DISP_ID, 1, 0); + SetDisplayDetectorMode(FRXDispID, 1, FWfDetector); + SetDisplayAverageMode(FRXDispID, 1, FWfAvgMode); + SetDisplayNumAverage(FRXDispID, 1, AvgCount); + SetDisplayAvBackmult(FRXDispID, 1, Backmult); + SetDisplayNormOneHz(FRXDispID, 1, 0); end; end.