From 24f74357cdc9b0bd6ca2f68c6f22bf9372a0977e Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 17 Jun 2026 12:06:59 +0300 Subject: [PATCH] feat: Pluto RX hw-gain control + AD9361 cold-init (FIR via libad9361) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RX hw-gain (Pluto/AD936x): backend SetRxGain(ModeIdx, GainDb) — gain_control_mode (manual/fast_attack/slow_attack/hybrid) + hardwaregain. Controller FRxGainMode/ FRxGainDb, commands SetRxGainMode/SetRxGain/RxGainBy, rfRxGain event, snapshot, applied in StartRunning. Persisted per-device (rx_gain_mode/rx_gain_db). HPSDR unaffected (backend no-op). - UI: separate "RF AGC" panel below the (unchanged) WDSP AGC block, shown only for Pluto — FAST/SLOW/HYB/MAN mode buttons + manual GAIN slider. LayoutLeftPanel reflows lower panels via RelayoutBelowBands; no layout shift on HPSDR. - Cold-init fix: direct sampling_frequency write doesn't load the AD9361 FIR decimation filter, so a cold-booted Pluto showed a wide centre spike that didn't track tuning (worked only after SDR Console pre-initialised it). Add optional libad9361 binding (dynamic) and call ad9361_set_bb_rate() in ApplySampleRate, which configures BBPLL + loads the FIR; falls back to direct write if absent. Co-Authored-By: Claude Opus 4.8 --- IIOBindings.pas | 31 +++++++++++++ MainForm.pas | 107 ++++++++++++++++++++++++++++++++++++++++++++ PlutoBackend.pas | 32 +++++++++++-- RadioBackend.pas | 7 +++ RadioController.pas | 47 +++++++++++++++++++ Settings.pas | 7 +++ 6 files changed, 228 insertions(+), 3 deletions(-) diff --git a/IIOBindings.pas b/IIOBindings.pas index b05ce4c..d6fd0eb 100644 --- a/IIOBindings.pas +++ b/IIOBindings.pas @@ -84,6 +84,13 @@ var // ---- Утилиты ---- iio_strerror: procedure(err: cint; dst: PAnsiChar; len: csize_t); cdecl; + // ---- libad9361 (опционально) ---- + // ad9361_set_bb_rate: настраивает BBPLL + ЗАГРУЖАЕТ FIR-таблицу под частоту. + // Без него прямая запись sampling_frequency на холодную даёт мусор (битый FIR). + // nil если libad9361 не загружена. + ad9361_set_bb_rate: function(dev: Piio_device; rate: culong): cint; cdecl; + ad9361_set_trx_fir_enable: function(dev: Piio_device; enable: cint): cint; cdecl; + // Загружает libiio (один раз). True — готово к использованию. Идемпотентно. function IIOLoad: Boolean; // True, если libiio загружена и символы разрешены. @@ -95,16 +102,20 @@ implementation var GLib: TLibHandle = NilHandle; + GLibAD: TLibHandle = NilHandle; // libad9361 (опционально) GLoaded: Boolean = False; const {$IFDEF WINDOWS} IIO_LIBS: array[0..1] of string = ('libiio.dll', 'iio.dll'); + AD_LIBS: array[0..1] of string = ('libad9361.dll', 'ad9361.dll'); {$ELSE} {$IFDEF DARWIN} IIO_LIBS: array[0..1] of string = ('libiio.dylib', 'libiio.1.dylib'); + AD_LIBS: array[0..1] of string = ('libad9361.dylib', 'libad9361.0.dylib'); {$ELSE} IIO_LIBS: array[0..1] of string = ('libiio.so', 'libiio.so.0'); + AD_LIBS: array[0..1] of string = ('libad9361.so', 'libad9361.so.0'); {$ENDIF} {$ENDIF} @@ -163,6 +174,21 @@ begin iio_buffer_step := Sym('iio_buffer_step'); iio_buffer_end := Sym('iio_buffer_end'); iio_strerror := Sym('iio_strerror'); + + // libad9361 — опционально (ad9361_set_bb_rate грузит FIR). Отсутствие не + // фатально: ApplySampleRate откатится на прямую запись sampling_frequency. + if GLibAD = NilHandle then + for I := 0 to High(AD_LIBS) do + begin + GLibAD := LoadLibrary(AD_LIBS[I]); + if GLibAD <> NilHandle then Break; + end; + if GLibAD <> NilHandle then + begin + ad9361_set_bb_rate := GetProcedureAddress(GLibAD, 'ad9361_set_bb_rate'); + ad9361_set_trx_fir_enable := GetProcedureAddress(GLibAD, 'ad9361_set_trx_fir_enable'); + end; + GLoaded := True; except on E: Exception do @@ -190,6 +216,11 @@ begin end; finalization + if GLibAD <> NilHandle then + begin + UnloadLibrary(GLibAD); + GLibAD := NilHandle; + end; if GLib <> NilHandle then begin UnloadLibrary(GLib); diff --git a/MainForm.pas b/MainForm.pas index 63b0dfc..9a02bdc 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -259,6 +259,11 @@ type BtnAGCMode: array[0..4] of TFlatButton; // FAST MED SLOW LONG OFF LblAGCTop: TLabel; // показывает значение уровня TrkAGC: TFlatSlider; + // RX hw-gain (Pluto) — ОТДЕЛЬНЫЙ блок под AGC (WDSP). Виден только для Pluto. + PanelPlutoAGC: TPanel; + BtnPlutoGain: array[0..3] of TFlatButton; // FAST/SLOW/HYB/MAN (Tag=режим) + TrkRxGain: TFlatSlider; // manual gain 0..73 dB + LblRxGain: TLabel; // значение усиления TrkVolume: TFlatSlider; BtnNR: TFlatButton; BtnNB: TFlatButton; @@ -328,6 +333,9 @@ type function ResolveDevice(const IP: string): THPSDRDevice; procedure RelabelBands; // подписи band-кнопок под план бэкенда procedure ApplyBackendCapsToUI; // sample-rate пресеты + гейтинг полей по Caps + procedure UpdateRxGainUI; // рендер RX hw-gain (Pluto) + procedure LayoutLeftPanel(Pluto: Boolean); // показать/скрыть Pluto-блок + reflow + procedure BtnPlutoGainClick(Sender: TObject); procedure DoConnectDevice(const Dev: THPSDRDevice); // Старт с уже выбранным устройством (preload rate + WDSP open + connect). // Переиспользуется десктоп-диалогом и web-оверлеем — без модального диалога. @@ -487,6 +495,8 @@ type procedure OnTXSettingsChange(const T: TTXSettings); procedure BtnAGCModeClick(Sender: TObject); procedure TrkAGCChange(Sender: TObject); + procedure TrkRxGainChange(Sender: TObject); + // (BtnPlutoGainClick объявлен выше) procedure MeterTimerTick(Sender: TObject); procedure SpectrumTimerTick(Sender: TObject); procedure RightPanelResize(Sender: TObject); @@ -1225,6 +1235,8 @@ begin for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); rfVolume: TrkVolume.Position := FController.FVolume; + rfRxGain: + UpdateRxGainUI; rfDrive: begin TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive% @@ -1397,6 +1409,7 @@ const BTN_H = 26; BTN_SM = 24; LEFT_W = 232; + PLUTO_AGC_H = 64; // высота отдельного Pluto RF-AGC блока (под AGC) TOP_VFO_W = 244; TOP_VFO_FREQ_H = 47; var @@ -1759,6 +1772,47 @@ begin Inc(Y, 74); + // --- Pluto RF AGC/gain — отдельный блок ПОД блоком AGC (WDSP). Виден только + // когда подключён Pluto-бэкенд; раскладку панелей ниже сдвигает LayoutLeftPanel. + PanelPlutoAGC := TPanel.Create(Self); + PanelPlutoAGC.Parent := PanelLeft; + PanelPlutoAGC.SetBounds(0, Y, LEFT_W, PLUTO_AGC_H); + PanelPlutoAGC.BevelOuter := bvNone; + PanelPlutoAGC.Visible := False; + MakeLbl(PanelPlutoAGC, 'RF AGC', 4, 2); + // Режимы AD936x: FAST/SLOW/HYB/MAN. Tag = индекс режима (1/2/3/0). + BtnPlutoGain[0] := MakeBtn(PanelPlutoAGC, 'FAST', + LeftPanelButtonLeft(LEFT_W, 4, 0), 16, LeftPanelButtonWidth(LEFT_W, 4, 0), + BTN_SM, BtnPlutoGainClick); + BtnPlutoGain[0].Tag := 1; + BtnPlutoGain[1] := MakeBtn(PanelPlutoAGC, 'SLOW', + LeftPanelButtonLeft(LEFT_W, 4, 1), 16, LeftPanelButtonWidth(LEFT_W, 4, 1), + BTN_SM, BtnPlutoGainClick); + BtnPlutoGain[1].Tag := 2; + BtnPlutoGain[2] := MakeBtn(PanelPlutoAGC, 'HYB', + LeftPanelButtonLeft(LEFT_W, 4, 2), 16, LeftPanelButtonWidth(LEFT_W, 4, 2), + BTN_SM, BtnPlutoGainClick); + BtnPlutoGain[2].Tag := 3; + BtnPlutoGain[3] := MakeBtn(PanelPlutoAGC, 'MAN', + LeftPanelButtonLeft(LEFT_W, 4, 3), 16, LeftPanelButtonWidth(LEFT_W, 4, 3), + BTN_SM, BtnPlutoGainClick); + BtnPlutoGain[3].Tag := 0; + // Manual gain slider. + MakeLbl(PanelPlutoAGC, 'GAIN', 4, 44); + TrkRxGain := TFlatSlider.Create(Self); + TrkRxGain.Parent := PanelPlutoAGC; TrkRxGain.Left := 56; TrkRxGain.Top := 40; + TrkRxGain.Width := LEFT_W - 96; TrkRxGain.Height := 16; + TrkRxGain.Min := 0; TrkRxGain.Max := 73; // AD936x hardwaregain dB + TrkRxGain.Position := FController.FRxGainDb; + TrkRxGain.OnChange := TrkRxGainChange; + LblRxGain := TLabel.Create(Self); + LblRxGain.Parent := PanelPlutoAGC; + LblRxGain.Left := LEFT_W - 38; LblRxGain.Top := 44; + LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb]); + LblRxGain.Font.Color := CLR_TEXT; LblRxGain.Font.Name := 'Courier New'; + LblRxGain.Font.Size := 7; + // НЕ Inc(Y): панель скрыта и наложена на позицию Bands; LayoutLeftPanel сдвинет. + // Band selector. Высота резервирует 2 дополнительных ряда под XVTR-кнопки // (создаются динамически в RebuildXvtrButtons). В каждом ряду до 6 кнопок, // итого до 12 XVTR-кнопок (наш CFG_XVTR_COUNT = 8 умещается с запасом). @@ -3540,6 +3594,10 @@ begin FSampleRateOverlay.SetRatePresetsDefault; // HPSDR 48k..1536k FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); end; + + // Отдельный Pluto RF-AGC блок под AGC: показываем и сдвигаем панели ниже. + LayoutLeftPanel(Caps.HasHWGain); + if Caps.HasHWGain then UpdateRxGainUI; end; procedure TMainForm.DoConnectDevice(const Dev: THPSDRDevice); @@ -5252,6 +5310,55 @@ begin FController.SetAGCTop(TrkAGC.Position); end; +procedure TMainForm.TrkRxGainChange(Sender: TObject); +begin + if FSyncingFromController then Exit; + FController.SetRxGain(TrkRxGain.Position); // переводит в manual +end; + +procedure TMainForm.BtnPlutoGainClick(Sender: TObject); +begin + if FSyncingFromController then Exit; + FController.SetRxGainMode((Sender as TFlatButton).Tag); // Tag = индекс режима +end; + +procedure TMainForm.UpdateRxGainUI; +// Рендер Pluto RF-AGC блока: подсветка режима + слайдер/значение manual gain. +var i: Integer; +begin + if PanelPlutoAGC = nil then Exit; + FSyncingFromController := True; + try + for i := 0 to 3 do + if BtnPlutoGain[i] <> nil then + StyleButton(BtnPlutoGain[i], BtnPlutoGain[i].Tag = FController.FRxGainMode); + TrkRxGain.Position := FController.FRxGainDb; + TrkRxGain.Enabled := (FController.FRxGainMode = 0); // активен только в manual + if FController.FRxGainMode = 0 then + LblRxGain.Caption := Format('%ddB', [FController.FRxGainDb]) + else + LblRxGain.Caption := 'AGC'; + finally + FSyncingFromController := False; + end; +end; + +procedure TMainForm.LayoutLeftPanel(Pluto: Boolean); +// Показывает/скрывает Pluto RF-AGC блок под AGC и сдвигает панели ниже +// (Bands и далее через RelayoutBelowBands). +var baseTop: Integer; +begin + if (PanelPlutoAGC = nil) or (PanelAGC = nil) or (PanelBands = nil) then Exit; + baseTop := PanelAGC.Top + PanelAGC.Height + 4; + PanelPlutoAGC.Top := baseTop; + PanelPlutoAGC.Visible := Pluto; + if Pluto then + PanelBands.Top := baseTop + PanelPlutoAGC.Height + 2 + else + PanelBands.Top := baseTop; + RelayoutBelowBands; +end; + // =========================================================================== // DSP/Audio callbacks — вызываются из рабочих потоков // =========================================================================== diff --git a/PlutoBackend.pas b/PlutoBackend.pas index 0443614..e51ed42 100644 --- a/PlutoBackend.pas +++ b/PlutoBackend.pas @@ -94,6 +94,7 @@ type Transmitting, PAEnabled, AlexEnabled: Boolean); override; procedure SendFullHP; override; procedure SendPTT(Active: Boolean); override; + procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); override; end; // Стабильный 6-байтовый ключ (synthetic MAC) из serial/URI — для per-device @@ -446,12 +447,25 @@ begin if (FPhy = nil) or (Hz < PLUTO_MIN_SR) then Exit; WasStreaming := FStreaming; if WasStreaming then StopRX; // менять rate с открытым буфером нельзя + + // Предпочтительно ad9361_set_bb_rate: настраивает BBPLL И загружает FIR-таблицу + // децимации под частоту. Без этого на холодную (битый/дефолтный FIR) спектр — + // широкая палка по центру, не реагирует на перестройку. Fallback — прямая + // запись sampling_frequency (работает, если FIR уже корректен). + if Assigned(ad9361_set_bb_rate) then + ad9361_set_bb_rate(FPhy, culong(Hz)) + else + begin + Ch := iio_device_find_channel(FPhy, 'voltage0', 0); + if Ch <> nil then + iio_channel_attr_write_longlong(Ch, 'sampling_frequency', Hz); + end; + + // RF-полоса отдельно (set_bb_rate её не трогает). Ch := iio_device_find_channel(FPhy, 'voltage0', 0); if Ch <> nil then - begin - iio_channel_attr_write_longlong(Ch, 'sampling_frequency', Hz); iio_channel_attr_write_longlong(Ch, 'rf_bandwidth', Hz); - end; + if WasStreaming then StartRX; end; @@ -552,4 +566,16 @@ begin // Реальный RX↔TX (вкл/выкл стримов) — фаза 4. end; +procedure TPlutoBackend.SetRxGain(ModeIdx: Integer; GainDb: Integer); +begin + case ModeIdx of + 1: FGainMode := 'fast_attack'; + 2: FGainMode := 'slow_attack'; + 3: FGainMode := 'hybrid'; + else FGainMode := 'manual'; + end; + FGainDb := GainDb; + if FConnected then ApplyGain; +end; + end. diff --git a/RadioBackend.pas b/RadioBackend.pas index 5a05fad..c38c1a7 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -134,6 +134,9 @@ type procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); virtual; procedure SendHighPriority(const Pkt: THighPriorityPacket); virtual; procedure SetStepAtten(dB: Byte); virtual; + // RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack, + // 3=hybrid. GainDb применяется только в manual. HPSDR — no-op. + procedure SetRxGain(ModeIdx: Integer; GainDb: Integer); virtual; procedure SetAlexConfig(const A: TAlexSettings); virtual; procedure SetXvtrMode(AEnable, ADisablePA: Boolean; ARxAnt: Byte); virtual; procedure SendDDCAudio(const LeftRight: array of SmallInt); virtual; @@ -204,6 +207,10 @@ procedure TRadioBackend.SetStepAtten(dB: Byte); begin end; +procedure TRadioBackend.SetRxGain(ModeIdx: Integer; GainDb: Integer); +begin +end; + procedure TRadioBackend.SetAlexConfig(const A: TAlexSettings); begin end; diff --git a/RadioController.pas b/RadioController.pas index be720e4..c3d4684 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -58,6 +58,7 @@ type rfVfoA, rfVfoB, rfActiveVfo, rfMode, rfFilter, rfFilterBW, rfAGCMode, rfAGCTop, + rfRxGain, // Pluto: hw-gain (auto/manual dB) rfVolume, rfMute, rfDrive, rfAtten, rfBand, rfXvtr, rfBandRestore, // rfBandRestore: явная смена диапазона (сброс Wf-avg в UI) rfCenterFreq, rfSpan, rfSampleRate, @@ -95,6 +96,8 @@ type ActiveVfo: Integer; Mode, Filter, FilterBW: Integer; AGCMode, AGCTop: Integer; + RxGainMode: Integer; + RxGainDb: Integer; Volume, Drive, Atten: Integer; Band, Xvtr: Integer; Muted, CTun, Duplex: Boolean; @@ -164,6 +167,8 @@ type FFilterBW: Integer; FAGCMode: Integer; FAGCTop: Integer; + FRxGainMode: Integer; // Pluto hw-gain: 0=manual,1=fast,2=slow,3=hybrid + FRxGainDb: Integer; // Pluto manual gain (дБ, 0..73) FCTun: Boolean; FCenterFreq: Double; FSpanHz: Double; @@ -395,6 +400,13 @@ type procedure SetAGCTop(DB: Integer); procedure AGCTopBy(Delta: Integer); + // RX hw-gain (Pluto): режим (0=manual,1=fast,2=slow,3=hybrid) / ручной дБ. + // На HPSDR — no-op в бэкенде. + procedure SetRxGainMode(M: Integer); + procedure SetRxGain(Db: Integer); + procedure RxGainBy(Delta: Integer); + procedure ApplyRxGainToBackend; // FRxGain* → FNetwork.SetRxGain + // Громкость / mute / drive / аттенюатор procedure SetVolume(V: Integer); procedure VolumeBy(Delta: Integer); @@ -486,6 +498,7 @@ begin FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0; FMode := 1; FFilter := 5; FFilterBW := 2700; FAGCMode := 1; FAGCTop := 90; + FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0; FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000; FCurrentBand := 5; FCurrentXvtr := -1; @@ -863,6 +876,7 @@ begin Result.SampleRate := FSampleRate; Result.ActiveVfo := FActiveVfo; Result.Mode := FMode; Result.Filter := FFilter; Result.FilterBW := FFilterBW; Result.AGCMode := FAGCMode; Result.AGCTop := FAGCTop; + Result.RxGainMode := FRxGainMode; Result.RxGainDb := FRxGainDb; Result.Volume := FVolume; Result.Drive := FDrivePercent; Result.Atten := FAtten; Result.Band := FCurrentBand; Result.Xvtr := FCurrentXvtr; Result.Muted := FMuted; Result.CTun := FCTun; Result.Duplex := FDisplayDuplex; @@ -1121,6 +1135,8 @@ begin Result.LastBand := FCurrentBand; Result.LastXvtr := FCurrentXvtr; Result.SampleRate := FSampleRate; + Result.RxGainMode := FRxGainMode; + Result.RxGainDb := FRxGainDb; if FWDSPReady then begin Result.FFTSize := FDSPEngine.FFTSize; @@ -1633,6 +1649,33 @@ end; procedure TRadioController.AGCTopBy(Delta: Integer); begin SetAGCTop(FAGCTop + Delta); end; +procedure TRadioController.ApplyRxGainToBackend; +begin + if Assigned(FNetwork) and FNetwork.Connected then + FNetwork.SetRxGain(FRxGainMode, FRxGainDb); +end; + +procedure TRadioController.SetRxGainMode(M: Integer); +begin + if M < 0 then M := 0 else if M > 3 then M := 3; + FRxGainMode := M; + ApplyRxGainToBackend; + Changed(rfRxGain); +end; + +procedure TRadioController.SetRxGain(Db: Integer); +begin + if Db < 0 then Db := 0 else if Db > 73 then Db := 73; + FRxGainDb := Db; + // Ручная установка усиления подразумевает manual-режим (0). + FRxGainMode := 0; + ApplyRxGainToBackend; + Changed(rfRxGain); +end; + +procedure TRadioController.RxGainBy(Delta: Integer); +begin SetRxGain(FRxGainDb + Delta); end; + procedure TRadioController.SetVolume(V: Integer); begin if V < 0 then V := 0 else if V > 100 then V := 100; @@ -2284,6 +2327,8 @@ begin FNetwork.ConfigureDDCs(1, FSampleRate div 1000, 0, FDitherEnabled, FRandomEnabled); // Применяем сохранённое состояние динамика/отправки аудио (мьют byte 1400 bit1). FNetwork.SetSpeakerAudio(FSendAudioToRadio); + // RX hw-gain в бэкенд (Pluto). HPSDR — no-op. + ApplyRxGainToBackend; SendDUCSpecificFromSettings; // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; @@ -2374,6 +2419,8 @@ begin FSampleRate := 1536000; FSpanHz := FSampleRate; end; + FRxGainMode := EnsureRange(FLoadedGlobal.RxGainMode, 0, 3); + FRxGainDb := EnsureRange(FLoadedGlobal.RxGainDb, 0, 73); FNRMode := EnsureRange(FLoadedGlobal.NRMode, 0, 4); FNBMode := EnsureRange(FLoadedGlobal.NBMode, 0, 2); FSNB := FLoadedGlobal.SNBEnabled; diff --git a/Settings.pas b/Settings.pas index d4e717d..9e16758 100644 --- a/Settings.pas +++ b/Settings.pas @@ -218,6 +218,8 @@ type LastBand: Integer; LastXvtr: Integer; // -1 = HF, 0..CFG_XVTR_COUNT-1 = активный трансвертер при выходе SampleRate: Integer; // глобальный — один для всех диапазонов + RxGainMode: Integer; // Pluto: 0=manual,1=fast,2=slow,3=hybrid + RxGainDb: Integer; // Pluto: manual gain дБ WindowLeft: Integer; WindowTop: Integer; WindowWidth: Integer; @@ -401,6 +403,7 @@ begin G.WfAGCEnabled := True; G.WfNFEnabled := False; G.SampleRate := 192000; + G.RxGainMode := 2; G.RxGainDb := 40; G.PAMaxPower := 100.0; for i := 0 to CFG_BAND_COUNT-1 do G.PABandCal[i] := CFG_PA_BAND_CAL_DEFAULT[i]; for i := 0 to CFG_XVTR_COUNT-1 do G.VHFBandCal[i] := CFG_VHF_CAL_DEFAULT; @@ -597,6 +600,8 @@ begin G.LastBand := JI(GObj,'last_band',5); G.LastXvtr := JI(GObj,'last_xvtr',-1); G.SampleRate := JI(GObj,'sample_rate',192000); + G.RxGainMode := JI(GObj,'rx_gain_mode',2); + G.RxGainDb := JI(GObj,'rx_gain_db',40); // Display settings G.FFTSize := JI(GObj,'fft_size',131072); G.WindowType := JI(GObj,'win_type',2); @@ -712,6 +717,8 @@ begin JW(O,'last_band',G.LastBand); JW(O,'last_xvtr',G.LastXvtr); JW(O,'sample_rate',G.SampleRate); + JW(O,'rx_gain_mode',G.RxGainMode); + JW(O,'rx_gain_db',G.RxGainDb); // Display settings JW(O,'fft_size',G.FFTSize); JW(O,'win_type',G.WindowType);