diff --git a/MainForm.pas b/MainForm.pas index eb1225f..394da30 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -347,6 +347,11 @@ type FPSPopup: TObject; // TPureSignalPopup (cast при использовании) PanelRXBlock: TPanel; + // Шаговый аттенюатор ADC (openHPSDR): плавные 0..31 дБ. На Pluto ряд + // скрыт — там усиление тракта живёт в блоке RF AGC. + LblAtt: TLabel; + LblAttVal: TLabel; + TrkAtt: TFlatSlider; BtnChannels: TFlatButton; FChannelsDropDown: TFlatDropDown; FChannelsForm: TObject; // TChannelsForm (cast при использовании) @@ -706,6 +711,8 @@ type procedure BtnAGCModeClick(Sender: TObject); procedure TrkAGCChange(Sender: TObject); procedure TrkRxGainChange(Sender: TObject); + procedure TrkAttChange(Sender: TObject); + procedure UpdateAttLabel; // (BtnPlutoGainClick объявлен выше) procedure MeterTimerTick(Sender: TObject); procedure SpectrumTimerTick(Sender: TObject); @@ -762,6 +769,13 @@ uses SettingsForm; {$R *.lfm} +const + // Высота RX-блока левой панели: с рядом ATT (openHPSDR) и без него (Pluto — + // там усиление тракта в блоке RF AGC). Переключает LayoutLeftPanel, он же + // двигает панели ниже. + RX_BLOCK_H = 96; + RX_BLOCK_H_ATT = 120; + function TMainForm.LeftPanelButtonWidth(PanelWidth, ColCount, ColIndex: Integer): Integer; var @@ -1505,6 +1519,12 @@ begin TrkVolume.Position := FController.ActiveVolume; // RX или TX-монитор rfRxGain: UpdateRxGainUI; + rfAtten: + if TrkAtt <> nil then + begin + TrkAtt.Position := FController.FAtten; + UpdateAttLabel; + end; rfDrive: begin TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive% @@ -2028,7 +2048,7 @@ begin // RX block: CTUN + VOL + NR/NB/SNB/ANF/MUTE PanelRXBlock := TPanel.Create(Self); PanelRXBlock.Parent := PanelLeft; - PanelRXBlock.SetBounds(0, Y, LEFT_W, 96); + PanelRXBlock.SetBounds(0, Y, LEFT_W, RX_BLOCK_H_ATT); PanelRXBlock.BevelOuter := bvNone; MakeLbl(PanelRXBlock, 'RX', 4, 2); @@ -2084,7 +2104,29 @@ begin LeftPanelButtonLeft(LEFT_W, 5, 4), 70, LeftPanelButtonWidth(LEFT_W, 5, 4), BTN_SM, BtnMuteClick); - Inc(Y, 100); + // Шаговый аттенюатор ADC — плавный 0..31 дБ (железо openHPSDR так и умеет, + // ступени 0/10/20 были только в web-UI). Показания S-метра/спектра на эти + // дБ компенсируются в контроллере, шкала dBm остаётся абсолютной. + LblAtt := TLabel.Create(Self); + LblAtt.Parent := PanelRXBlock; LblAtt.Left := 4; LblAtt.Top := 106; + LblAtt.Caption := 'ATT'; LblAtt.Font.Color := CLR_TEXTDIM; + LblAtt.Font.Name := 'Courier New'; LblAtt.Font.Size := 7; + + TrkAtt := TFlatSlider.Create(Self); + TrkAtt.Parent := PanelRXBlock; TrkAtt.Left := 34; TrkAtt.Top := 102; + TrkAtt.Width := LEFT_W - 76; TrkAtt.Height := 16; + TrkAtt.Min := 0; TrkAtt.Max := 31; // ADC step attenuator, дБ + TrkAtt.Position := FController.FAtten; + TrkAtt.OnChange := TrkAttChange; + + LblAttVal := TLabel.Create(Self); + LblAttVal.Parent := PanelRXBlock; + LblAttVal.Left := LEFT_W - 38; LblAttVal.Top := 106; + LblAttVal.Font.Color := CLR_TEXT; LblAttVal.Font.Name := 'Courier New'; + LblAttVal.Font.Size := 7; + UpdateAttLabel; + + Inc(Y, RX_BLOCK_H_ATT + 4); // AGC controls PanelAGC := TPanel.Create(Self); @@ -7933,6 +7975,19 @@ begin FController.SetRxGain(TrkRxGain.Position); // переводит в manual end; +procedure TMainForm.TrkAttChange(Sender: TObject); +begin + if FSyncingFromController then Exit; + FController.SetAtten(TrkAtt.Position); + UpdateAttLabel; +end; + +procedure TMainForm.UpdateAttLabel; +begin + if LblAttVal <> nil then + LblAttVal.Caption := Format('%ddB', [FController.FAtten]); +end; + procedure TMainForm.BtnPlutoGainClick(Sender: TObject); begin if FSyncingFromController then Exit; @@ -7966,6 +8021,17 @@ procedure TMainForm.LayoutLeftPanel(Pluto: Boolean); var baseTop: Integer; begin if (PanelPlutoAGC = nil) or (PanelAGC = nil) or (PanelBands = nil) then Exit; + // Шаговый аттенюатор — только у openHPSDR; у AD936x усиление тракта в блоке + // RF AGC ниже, поэтому ряд ATT убираем и RX-блок ужимаем. + if (TrkAtt <> nil) and (LblAtt <> nil) and (LblAttVal <> nil) then + begin + TrkAtt.Visible := not Pluto; + LblAtt.Visible := not Pluto; + LblAttVal.Visible := not Pluto; + if Pluto then PanelRXBlock.Height := RX_BLOCK_H + else PanelRXBlock.Height := RX_BLOCK_H_ATT; + PanelAGC.Top := PanelRXBlock.Top + PanelRXBlock.Height + 4; + end; baseTop := PanelAGC.Top + PanelAGC.Height + 4; PanelPlutoAGC.Top := baseTop; PanelPlutoAGC.Visible := Pluto; diff --git a/RadioController.pas b/RadioController.pas index a5147c2..04c3c28 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -601,7 +601,7 @@ type procedure ToggleMute; procedure SetDrive(V: Integer); procedure DriveBy(Delta: Integer); - procedure SetAtten(Idx: Integer); + procedure SetAtten(Db: Integer); // ADC step attenuator, дБ (0..31) // ---- Мультислайсы (софт-fan-out) ---- // Создаёт слайс на TargetHz: заводит WDSP-канал в движке + свой аудио-выход @@ -635,6 +635,8 @@ type // Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан. // Колбэк движка (display-поток), см. TOnDispCalQuery. function DispCalFor(PanId: Integer): Double; + // Поправка на шаговый аттенюатор ADC (дБ) — входит в оба офсета выше. + function AttenCalDB: Double; procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string); function SliceCount: Integer; function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean; @@ -2094,7 +2096,17 @@ end; function TRadioController.SMeterCalFor(VisibleHz: Double): Double; begin - Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB); + Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB) + + AttenCalDB; +end; + +function TRadioController.AttenCalDB: Double; +// Шаговый аттенюатор ADC режет вход — чтобы шкала dBm оставалась абсолютной, +// его дБ возвращаются в показания (так же делают Thetis и piHPSDR). У AD936x +// такого аттенюатора нет: там усиление тракта — hardwaregain. +begin + if IsPluto then Result := 0.0 + else Result := FAtten; end; function TRadioController.DispCalFor(PanId: Integer): Double; @@ -2105,7 +2117,7 @@ begin if (PanId > 0) and (PanId < MAX_PANS) then Hz := FPanFreqHz[PanId] else Hz := FCenterFreq; if Hz <= 0 then Exit(0.0); - Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB); + Result := CalLookup(Hz, FCal.DispBandDB, FCal.DispXvtrDB) + AttenCalDB; end; procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer; @@ -2808,6 +2820,7 @@ begin // Аппаратный AGC AD936x — на диапазон (усиление тракта разное на 2м и QO-100). Result.RxGainMode := FRxGainMode; Result.RxGainDb := FRxGainDb; + Result.AttenDB := FAtten; // шаговый аттенюатор ADC — тоже на диапазон // Waterfall AGC/NF — глобальные (не диапазонные) end; @@ -2846,6 +2859,7 @@ begin FXvtrSettings.Entries[i].LastPanSlider := FPanSlider; FXvtrSettings.Entries[i].LastRxGainMode := FRxGainMode; FXvtrSettings.Entries[i].LastRxGainDb := FRxGainDb; + FXvtrSettings.Entries[i].LastAttenDB := FAtten; end; FSettings.SaveXvtr(FDevMAC, FXvtrSettings); Exit; @@ -2975,6 +2989,11 @@ begin ApplyRxGainToBackend; Changed(rfRxGain); // UI: кнопки FAST/SLOW/HYB/MAN + ползунок + // --- Шаговый аттенюатор ADC (openHPSDR) — на диапазон/слот трансвертера. + FAtten := EnsureRange(B.AttenDB, 0, 31); + if Assigned(FNetwork) then FNetwork.SetStepAtten(Byte(FAtten)); + Changed(rfAtten); + // --- FM squelch / CTCSS / step / repeater --- FFMSQOn := B.FMSQOn; FFMSQLevel := B.FMSQLevel; @@ -3646,12 +3665,20 @@ begin else FDSPEngine.SetDriveLevel(EnsureRange(Pct, 0, 100) / 100.0); end; -procedure TRadioController.SetAtten(Idx: Integer); +procedure TRadioController.SetAtten(Db: Integer); +// Шаговый аттенюатор ADC — плавный 0..31 дБ (как в железе openHPSDR), а не +// три фиксированные ступени. Уровни S-метра/спектра компенсируются на ту же +// величину (CalLookup), поэтому шкала dBm остаётся абсолютной. begin - if Idx < 0 then Idx := 0 else if Idx > 2 then Idx := 2; - FAtten := Idx; - if Assigned(FNetwork) then FNetwork.SetStepAtten(FAtten * 10); + FAtten := EnsureRange(Db, 0, 31); + if Assigned(FNetwork) then FNetwork.SetStepAtten(Byte(FAtten)); Changed(rfAtten); + // Персист — вместе с диапазоном/слотом (SaveCurrentBand на выходе из них), + // писать JSON на каждый шаг ползунка незачем, как и у drive. + if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then + FXvtrSettings.Entries[FCurrentXvtr].LastAttenDB := FAtten + else if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then + FBandCache[FCurrentBand].AttenDB := FAtten; end; procedure TRadioController.SetBand(Idx: Integer); @@ -3742,6 +3769,7 @@ begin B.FMRptOffsetHz := E.LastFMRptOffsetHz; B.RxGainMode := E.LastRxGainMode; // hw-AGC слота (AD936x) B.RxGainDb := E.LastRxGainDb; + B.AttenDB := E.LastAttenDB; // аттенюатор ADC слота B.VfoA := Vis; B.VfoB := FVfoB; ApplyBandDSP(B); diff --git a/Settings.pas b/Settings.pas index bb96fda..74cb5fe 100644 --- a/Settings.pas +++ b/Settings.pas @@ -103,6 +103,9 @@ type // сильно разное на 2м и на QO-100, поэтому режим и manual-гейн per-band. RxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid RxGainDb: Integer; // manual gain, дБ (0..73) + // Шаговый аттенюатор ADC (openHPSDR) — на диапазон: на 40м ночью нужен, + // на 10м нет. 0..31 дБ. + AttenDB: Integer; end; // Персист панадаптеров и слайсов (секция "pans" под MAC). Восстанавливается @@ -295,6 +298,7 @@ type // Аппаратный AGC AD936x на слот трансвертера (у QO-100 своё усиление тракта). LastRxGainMode: Integer; // 0=manual,1=fast,2=slow,3=hybrid LastRxGainDb: Integer; // manual gain, дБ + LastAttenDB: Integer; // шаговый аттенюатор ADC слота, дБ (0..31) end; TXvtrSettings = record @@ -558,6 +562,7 @@ begin B.SpanHz := 192000; B.RxGainMode := 2; // slow_attack (как глобальный дефолт Pluto) B.RxGainDb := 40; + B.AttenDB := 0; B.FMSQOn := False; B.FMSQLevel := 30; B.CTCSSOn := False; @@ -944,6 +949,7 @@ begin Bands[i].FMRptOffsetHz := JD(BObj,'fmrpt_offset', Bands[i].FMRptOffsetHz); Bands[i].RxGainMode := EnsureRange(JI(BObj,'rx_gain_mode', Bands[i].RxGainMode), 0, 3); Bands[i].RxGainDb := EnsureRange(JI(BObj,'rx_gain_db', Bands[i].RxGainDb), 0, 73); + Bands[i].AttenDB := EnsureRange(JI(BObj,'atten_db', Bands[i].AttenDB), 0, 31); end; end; @@ -1060,6 +1066,7 @@ begin JW(O,'fmrpt_dir',B.FMRptDir); JW(O,'fmrpt_offset',B.FMRptOffsetHz); JW(O,'zoom_factor',B.ZoomFactor); JW(O,'pan_slider',B.PanSlider); JW(O,'rx_gain_mode',B.RxGainMode); JW(O,'rx_gain_db',B.RxGainDb); + JW(O,'atten_db',B.AttenDB); end; function TSettingsManager.LoadBand(const MAC: array of Byte; BandIdx: Integer; @@ -1091,6 +1098,7 @@ begin B.FMRptOffsetHz := JD(O,'fmrpt_offset', B.FMRptOffsetHz); B.RxGainMode := EnsureRange(JI(O,'rx_gain_mode', B.RxGainMode), 0, 3); B.RxGainDb := EnsureRange(JI(O,'rx_gain_db', B.RxGainDb), 0, 73); + B.AttenDB := EnsureRange(JI(O,'atten_db', B.AttenDB), 0, 31); B.ZoomFactor := EnsureRange(JD(O,'zoom_factor', B.ZoomFactor), 0.0, 1.0); B.PanSlider := EnsureRange(JD(O,'pan_slider', B.PanSlider), 0.0, 1.0); end; @@ -1617,6 +1625,7 @@ begin X.Entries[i].LastPanSlider := 0.5; X.Entries[i].LastRxGainMode := 2; // slow_attack X.Entries[i].LastRxGainDb := 40; + X.Entries[i].LastAttenDB := 0; end; // Шаблоны (пользователь должен сам Enable, чтобы трансверторы появились) X.Entries[0].ButtonText := '2m'; @@ -1698,6 +1707,7 @@ begin X.Entries[i].LastPanSlider := EnsureRange(JD(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider), 0.0, 1.0); X.Entries[i].LastRxGainMode := EnsureRange(JI(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode), 0, 3); X.Entries[i].LastRxGainDb := EnsureRange(JI(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb), 0, 73); + X.Entries[i].LastAttenDB := EnsureRange(JI(EObj, 'last_atten_db', X.Entries[i].LastAttenDB), 0, 31); // Валидация: Last freqs должны быть в [FreqBegin..FreqEnd] if (X.Entries[i].LastFreq < X.Entries[i].FreqBegin) or (X.Entries[i].LastFreq > X.Entries[i].FreqEnd) then @@ -1798,6 +1808,7 @@ begin JW(EObj, 'last_pan_slider', X.Entries[i].LastPanSlider); JW(EObj, 'last_rx_gain_mode', X.Entries[i].LastRxGainMode); JW(EObj, 'last_rx_gain_db', X.Entries[i].LastRxGainDb); + JW(EObj, 'last_atten_db', X.Entries[i].LastAttenDB); end; end; diff --git a/WebPageHtml.pas b/WebPageHtml.pas index 360d598..77b49b7 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -199,7 +199,7 @@ begin '' + '' + '' + - '' + + '