feature(rx): плавный аттенюатор 0..31 дБ в UI, память на диапазон, компенсация уровней

Аттенюатор был только в web и только тремя ступенями (0/-10/-20), а его дБ
никуда не возвращались — при ATT 20 дБ S-метр и спектр уезжали на 20 дБ, и
свежая калибровка уровней была верна лишь при ATT=0.

- RadioController: SetAtten(Db) 0..31 (как умеет железо) вместо индекса *10.
  AttenCalDB входит в SMeterCalFor и DispCalFor — стрелка, спектр, водопад и
  паны компенсируются, шкала dBm остаётся абсолютной (как Thetis/piHPSDR).
  У AD936x аттенюатора нет, там поправка 0 (усиление тракта — hardwaregain).
- Память на диапазон/слот трансвертера по образцу RxGainMode/RxGainDb:
  TBandSettings.AttenDB (band 'atten_db') и TXvtrEntry.LastAttenDB
  ('last_atten_db'); восстановление и отправка в железо — в ApplyBandDSP,
  сбор — в MakeBandSettings/SaveCurrentBand, вход в слот — через B.AttenDB.
  SetAtten сразу кладёт значение в кэш диапазона/слота (JSON пишется общим
  сохранением, а не на каждый шаг ползунка — как у drive).
- MainForm: ряд «ATT [ползунок] NNdB» в RX-блоке левой панели, отступы как у
  VOL; на Pluto ряд скрыт и RX-блок ужимается (LayoutLeftPanel двигает
  панели ниже). Внешние изменения приходят по rfAtten.
- Web: select → ползунок 0..31 с цифрой, attn_idx -> attn_db, cmd attn {db:}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 11:41:21 +03:00
co-authored by Claude Opus 4.8
parent 366d4c9821
commit 6de8c00689
5 changed files with 127 additions and 22 deletions
+35 -7
View File
@@ -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);