feature(calibration): офсет спектра/водопада на диапазон и на трансвертер

Вторая калибровочная таблица рядом с S-метром (паритет с Display Cal Offset
в Thetis): у display-тракта своё окно/детектор, и его уровень расходится с
показаниями стрелки на пару дБ, поэтому таблицы раздельные.

- Settings: DispBandDB[12] / DispXvtrDB[8] (JSON cal_disp_band_i /
  cal_disp_xvtr_i, дефолт 0 — картинка не меняется до первой правки).
- WDSPEngine: колбэк OnDispCal(PanId) — движок раз в кадр спрашивает офсет в
  UpdateSpectrum и прибавляет к пикселям (главный спектр/водопад в копирующем
  цикле, паны in-place при ненулевом офсете). На TX-дисплее не применяется.
  Так обошлись без хуков на каждое присваивание FCenterFreq.
- RadioController: общий CalLookup(Hz, BandTbl, XvtrTbl) — на нём SMeterCalFor
  и DispCalFor(PanId): главный дисплей по FCenterFreq, пан по FPanFreqHz[p],
  т.е. кросс-банд паны получают свой офсет. Колбэк вешается в CreateEngines,
  поэтому работает и в GUI, и в ewsdrd; web-зеркало получает готовые кадры.
- SettingsForm: группа «Spectrum / waterfall» с той же разбивкой (диапазоны +
  включённые трансвертеры активного железа) и кнопка «= S-meter» — копия
  таблицы стрелки как старт калибровки; общая раскладка сеток вынесена в
  LayoutCalOffsets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 11:16:34 +03:00
co-authored by Claude Opus 4.8
parent 797bfc2e7c
commit 366d4c9821
4 changed files with 244 additions and 61 deletions
+31 -4
View File
@@ -452,6 +452,10 @@ type
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
procedure OnDemodAudioReady(const Left, Right: array of Single; Count: Integer);
procedure OnDMRAudioReady(const PCM: array of Single; Count: Integer);
// Общая выборка калибровочной таблицы «диапазон/трансвертер» по частоте.
function CalLookup(VisibleHz: Double;
const BandTbl: array of Double;
const XvtrTbl: array of Double): Double;
procedure OnSpectrumReady(const Pixels: array of Single; Count: Integer);
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
procedure OnMicPacket(const Data: TMicDataPacket);
@@ -628,6 +632,9 @@ type
function ReadSMeterDBm: Double;
// Офсет S-метра (дБ) для видимой частоты: слот трансвертера или диапазон.
function SMeterCalFor(VisibleHz: Double): Double;
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
// Колбэк движка (display-поток), см. TOnDispCalQuery.
function DispCalFor(PanId: Integer): Double;
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
function SliceCount: Integer;
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
@@ -975,6 +982,9 @@ begin
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
FDSPEngine.OnSliceDemodAudio := OnSliceDemodAudioReady;
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
// Калибровка уровня спектра/водопада на диапазон/трансвертер — движок
// спрашивает её раз в кадр (сам он про диапазоны не знает).
FDSPEngine.OnDispCal := DispCalFor;
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
@@ -2060,7 +2070,8 @@ begin
Result := FDSPEngine.GetSMeterDBm + SMeterCalFor(ActiveVfoHz);
end;
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
function TRadioController.CalLookup(VisibleHz: Double;
const BandTbl: array of Double; const XvtrTbl: array of Double): Double;
// Трансвертер имеет приоритет над диапазоном: его окно частот — видимые
// (не IF), и усиление тракта задаёт конвертер/LNB, а не КВ-вход трансивера.
// Индекс диапазона — по плану активного железа (HF openHPSDR / VHF-UHF Pluto).
@@ -2071,14 +2082,30 @@ begin
and FXvtrSettings.Entries[FCurrentXvtr].Enabled
and (VisibleHz >= FXvtrSettings.Entries[FCurrentXvtr].FreqBegin)
and (VisibleHz <= FXvtrSettings.Entries[FCurrentXvtr].FreqEnd) then
Exit(FCal.SMeterXvtrDB[FCurrentXvtr]);
Exit(XvtrTbl[FCurrentXvtr]);
for i := 0 to CFG_XVTR_COUNT-1 do
if FXvtrSettings.Entries[i].Enabled
and (VisibleHz >= FXvtrSettings.Entries[i].FreqBegin)
and (VisibleHz <= FXvtrSettings.Entries[i].FreqEnd) then
Exit(FCal.SMeterXvtrDB[i]);
Exit(XvtrTbl[i]);
Bi := FreqToBandIdx(VisibleHz, IsPluto);
if (Bi >= 0) and (Bi < CFG_BAND_COUNT) then Result := FCal.SMeterBandDB[Bi];
if (Bi >= 0) and (Bi < CFG_BAND_COUNT) then Result := BandTbl[Bi];
end;
function TRadioController.SMeterCalFor(VisibleHz: Double): Double;
begin
Result := CalLookup(VisibleHz, FCal.SMeterBandDB, FCal.SMeterXvtrDB);
end;
function TRadioController.DispCalFor(PanId: Integer): Double;
// Зовётся из display-потока движка раз в кадр. Главный дисплей калибруем по
// центру полосы, доп. пан — по своей частоте (паны бывают кросс-банд).
var Hz: Double;
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);
end;
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
+16
View File
@@ -345,6 +345,11 @@ type
// Pluto); таблицы не пересекаются, т.к. секция JSON ключуется по MAC.
SMeterBandDB: array[0..CFG_BAND_COUNT-1] of Double;
SMeterXvtrDB: array[0..CFG_XVTR_COUNT-1] of Double;
// Офсет спектра/водопада, дБ — та же разбивка. Отдельно от S-метра (как
// Display Cal Offset в Thetis): у display-тракта своё окно/детектор, и его
// уровень отличается от показаний стрелки на пару дБ.
DispBandDB: array[0..CFG_BAND_COUNT-1] of Double;
DispXvtrDB: array[0..CFG_XVTR_COUNT-1] of Double;
end;
TGlobalSettings = record
@@ -905,6 +910,13 @@ begin
for i := 0 to CFG_XVTR_COUNT-1 do
G.Cal.SMeterXvtrDB[i] :=
EnsureRange(JD(GObj,'cal_smeter_xvtr_'+IntToStr(i), SMLegacy), -30.0, 30.0);
// Спектр/водопад: своя таблица офсетов (дефолт 0 — как было).
for i := 0 to CFG_BAND_COUNT-1 do
G.Cal.DispBandDB[i] :=
EnsureRange(JD(GObj,'cal_disp_band_'+IntToStr(i), 0.0), -30.0, 30.0);
for i := 0 to CFG_XVTR_COUNT-1 do
G.Cal.DispXvtrDB[i] :=
EnsureRange(JD(GObj,'cal_disp_xvtr_'+IntToStr(i), 0.0), -30.0, 30.0);
for i := 0 to CFG_BAND_COUNT-1 do
begin
@@ -1025,6 +1037,10 @@ begin
JW(O,'cal_smeter_band_'+IntToStr(i),G.Cal.SMeterBandDB[i]);
for i := 0 to CFG_XVTR_COUNT-1 do
JW(O,'cal_smeter_xvtr_'+IntToStr(i),G.Cal.SMeterXvtrDB[i]);
for i := 0 to CFG_BAND_COUNT-1 do
JW(O,'cal_disp_band_'+IntToStr(i),G.Cal.DispBandDB[i]);
for i := 0 to CFG_XVTR_COUNT-1 do
JW(O,'cal_disp_xvtr_'+IntToStr(i),G.Cal.DispXvtrDB[i]);
end;
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
+169 -55
View File
@@ -175,6 +175,14 @@ type
FLblSMXvtr: array[0..CFG_XVTR_COUNT-1] of TLabel;
FLblSMBandHdr: TLabel;
FLblSMXvtrHdr: TLabel;
FCalGrpDisp: TPanel; // Spectrum / waterfall
FEdDispBand: array[0..CFG_BAND_COUNT-1] of TFlatFloatSpinEdit; // офсет спектра на диапазон
FLblDispBand: array[0..CFG_BAND_COUNT-1] of TLabel;
FEdDispXvtr: array[0..CFG_XVTR_COUNT-1] of TFlatFloatSpinEdit; // офсет спектра на трансвертер
FLblDispXvtr: array[0..CFG_XVTR_COUNT-1] of TLabel;
FLblDispBandHdr: TLabel;
FLblDispXvtrHdr: TLabel;
FBtnDispFromSM: TFlatButton; // «= S-meter» — копия таблицы
FCalIsPluto: Boolean; // активное железо — AD936x
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
@@ -370,6 +378,15 @@ type
procedure BuildPATab;
procedure BuildCalibrationTab;
procedure RelayoutCalTab;
// Раскладка блока офсетов «на диапазон + на трансвертер» внутри группы
// (общая для S-метра и спектра). Возвращает нужную высоту группы.
function LayoutCalOffsets(const BandLbl: array of TLabel;
const BandEd: array of TFlatFloatSpinEdit;
XvtrHdr: TLabel;
const XvtrLbl: array of TLabel;
const XvtrEd: array of TFlatFloatSpinEdit;
Y0: Integer): Integer;
procedure OnCalDispFromSMClick(Sender: TObject);
procedure CollectCalFromUI;
procedure OnCalAnyChange(Sender: TObject);
procedure BuildCATTab;
@@ -2066,6 +2083,53 @@ begin
FEdSMXvtr[i].Tag := i;
end;
// ── Spectrum / waterfall ────────────────────────────────────────────────
// Отдельная от S-метра калибровка (Display Cal Offset в Thetis): у display-
// тракта своё окно/детектор, поэтому уровень трассы и показания стрелки
// расходятся на пару дБ. Сдвиг двигает ВСЮ трассу (и шумовую полку, и
// сигналы) — это калибровка уровня, а не подавление шума.
FCalGrpDisp := MakeGroupPanel(FPageCalib, 'Spectrum / waterfall',
CAL_MARGIN, GTop, CAL_GRP_W, GRP3_H0);
Grp := FCalGrpDisp;
FLblDispBandHdr := MakeLbl(Grp,
'Offset (dB) added to spectrum/waterfall levels — per band (moves the whole '
+ 'trace incl. noise floor).', CAL_PAD, 30, 650);
FBtnDispFromSM := TFlatButton.Create(Self);
FBtnDispFromSM.Parent := Grp;
FBtnDispFromSM.Caption := '= S-meter';
FBtnDispFromSM.SetBounds(CAL_GRP_W - 130, 24, 112, BTN_H);
FBtnDispFromSM.OnClick := OnCalDispFromSMClick;
FBtnDispFromSM.ClrNorm := CLR_PANEL;
FBtnDispFromSM.ClrBorder := TColor($00404040);
FBtnDispFromSM.ClrHot := TColor($00303030);
FBtnDispFromSM.ClrActive := TColor($00003300);
FBtnDispFromSM.ClrText := CLR_TEXT;
FBtnDispFromSM.ClrTextAct := CLR_ACCENT;
FBtnDispFromSM.Font.Name := UI_FONT;
FBtnDispFromSM.Font.Size := 9;
FBtnDispFromSM.Hint := 'Copy the S-meter offsets into this table';
FBtnDispFromSM.ShowHint := True;
for i := 0 to CFG_BAND_COUNT - 1 do
begin
FLblDispBand[i] := MakeLbl(Grp, '', 0, 0, CAL_SM_LBL_W);
FEdDispBand[i] := MakeCalSpin(Grp, 0, 0, -30.0, 30.0, 0.1, 1, 0.0);
FEdDispBand[i].Tag := i;
end;
FLblDispXvtrHdr := MakeLbl(Grp, 'Per transverter slot', CAL_PAD, 0, 300);
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
FLblDispXvtr[i] := MakeLbl(Grp, '', 0, 0, CAL_SM_LBL_W);
FLblDispXvtr[i].AutoSize := False;
FLblDispXvtr[i].ShowHint := True;
FEdDispXvtr[i] := MakeCalSpin(Grp, 0, 0, -30.0, 30.0, 0.1, 1, 0.0);
FEdDispXvtr[i].Tag := i;
end;
RelayoutCalTab; // стартовая раскладка (HF-план, трансвертеры скрыты)
FLoading := False;
@@ -2077,23 +2141,21 @@ procedure TSettingsForm.RelayoutCalTab;
// датчики питания — свойства Alex-моста openHPSDR, у AD936x такой телеметрии
// нет, поэтому обе группы прячутся, а S-метр поднимается наверх.
var
i, r, c, X, Y, GTop, BandRows, XvtrRows, Slot, H: Integer;
i, r, c, X, Y, GTop, HSM, HDisp: Integer;
Nm: string;
begin
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
or (FLblSMXvtrHdr = nil) then Exit;
or (FCalGrpDisp = nil) or (FLblDispXvtrHdr = nil) then Exit;
// ---- строки диапазонов: позиция по индексу плана, пустые слоты скрыты ----
BandRows := 0;
// ---- подписи/видимость строк Det. Cal. (своя сетка, уже с подсказкой) ----
for i := 0 to CFG_BAND_COUNT - 1 do
begin
Nm := BandName(i, FCalIsPluto);
r := i div CAL_COLS;
c := i mod CAL_COLS;
X := CAL_PAD + c * (CAL_LBL_W + CAL_ED_W + 18);
Y := CAL_R1 + CAL_ROW_H + 46 + r * CAL_ROW_H;
X := CAL_PAD + c * (CAL_LBL_W + CAL_ED_W + 18);
Y := CAL_R1 + CAL_ROW_H + 46 + r * CAL_ROW_H;
if FLblPADetCal[i] <> nil then
begin
FLblPADetCal[i].Caption := Nm + ':';
@@ -2105,51 +2167,13 @@ begin
FEdPADetCal[i].SetBounds(X + CAL_LBL_W, Y, CAL_ED_W, BTN_H + 2);
FEdPADetCal[i].Visible := Nm <> '';
end;
X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
Y := 52 + r * CAL_ROW_H;
if FLblSMBand[i] <> nil then
begin
FLblSMBand[i].Caption := Nm + ':';
FLblSMBand[i].SetBounds(X, Y + 5, CAL_SM_LBL_W, 18);
FLblSMBand[i].Visible := Nm <> '';
end;
if FEdSMBand[i] <> nil then
begin
FEdSMBand[i].SetBounds(X + CAL_SM_LBL_W, Y, CAL_ED_W, BTN_H + 2);
FEdSMBand[i].Visible := Nm <> '';
end;
if Nm <> '' then BandRows := r + 1;
end;
// ---- строки трансвертеров: только включённые слоты, без дырок ----
Y := 52 + BandRows * CAL_ROW_H + 6;
FLblSMXvtrHdr.SetBounds(CAL_PAD, Y, 300, 18);
Slot := 0;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
if FCalXvtrOn[i] then
begin
r := Slot div CAL_COLS;
c := Slot mod CAL_COLS;
X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
if FCalXvtrName[i] <> '' then Nm := FCalXvtrName[i]
else Nm := 'XVTR' + IntToStr(i);
FLblSMXvtr[i].Caption := Nm + ':';
FLblSMXvtr[i].Hint := 'XVTR' + IntToStr(i) + ' (' + Nm + ')';
FLblSMXvtr[i].SetBounds(X, Y + 24 + r * CAL_ROW_H + 5, CAL_SM_LBL_W, 18);
FEdSMXvtr[i].SetBounds(X + CAL_SM_LBL_W, Y + 24 + r * CAL_ROW_H,
CAL_ED_W, BTN_H + 2);
Inc(Slot);
end;
FLblSMXvtr[i].Visible := FCalXvtrOn[i];
FEdSMXvtr[i].Visible := FCalXvtrOn[i];
end;
XvtrRows := (Slot + CAL_COLS - 1) div CAL_COLS;
FLblSMXvtrHdr.Visible := Slot > 0;
if Slot > 0 then H := Y + 24 + XvtrRows * CAL_ROW_H + 12
else H := Y + 12;
// ---- таблицы офсетов (S-метр и спектр) — одинаковая геометрия ----
HSM := LayoutCalOffsets(FLblSMBand, FEdSMBand, FLblSMXvtrHdr,
FLblSMXvtr, FEdSMXvtr, 52);
HDisp := LayoutCalOffsets(FLblDispBand, FEdDispBand, FLblDispXvtrHdr,
FLblDispXvtr, FEdDispXvtr, 52);
// ---- вертикальная укладка групп ----
GTop := 86;
@@ -2162,7 +2186,89 @@ begin
FCalGrpVA.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
GTop := GTop + CAL_GRP2_H + 14;
end;
FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, H);
FCalGrpSM.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HSM);
GTop := GTop + HSM + 14;
FCalGrpDisp.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, HDisp);
end;
function TSettingsForm.LayoutCalOffsets(const BandLbl: array of TLabel;
const BandEd: array of TFlatFloatSpinEdit; XvtrHdr: TLabel;
const XvtrLbl: array of TLabel; const XvtrEd: array of TFlatFloatSpinEdit;
Y0: Integer): Integer;
// Строки диапазонов стоят по индексу плана (пустой слот просто скрыт),
// слоты трансвертеров — только включённые и без дырок.
var
i, r, c, X, Y, Slot, BandRows, XvtrRows: Integer;
Nm: string;
begin
BandRows := 0;
for i := 0 to CFG_BAND_COUNT - 1 do
begin
Nm := BandName(i, FCalIsPluto);
r := i div CAL_COLS;
c := i mod CAL_COLS;
X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
Y := Y0 + r * CAL_ROW_H;
if BandLbl[i] <> nil then
begin
BandLbl[i].Caption := Nm + ':';
BandLbl[i].SetBounds(X, Y + 5, CAL_SM_LBL_W, 18);
BandLbl[i].Visible := Nm <> '';
end;
if BandEd[i] <> nil then
begin
BandEd[i].SetBounds(X + CAL_SM_LBL_W, Y, CAL_ED_W, BTN_H + 2);
BandEd[i].Visible := Nm <> '';
end;
if Nm <> '' then BandRows := r + 1;
end;
Y := Y0 + BandRows * CAL_ROW_H + 6;
XvtrHdr.SetBounds(CAL_PAD, Y, 300, 18);
Slot := 0;
for i := 0 to CFG_XVTR_COUNT - 1 do
begin
if FCalXvtrOn[i] then
begin
r := Slot div CAL_COLS;
c := Slot mod CAL_COLS;
X := CAL_PAD + c * (CAL_SM_LBL_W + CAL_ED_W + 14);
if FCalXvtrName[i] <> '' then Nm := FCalXvtrName[i]
else Nm := 'XVTR' + IntToStr(i);
XvtrLbl[i].Caption := Nm + ':';
XvtrLbl[i].Hint := 'XVTR' + IntToStr(i) + ' (' + Nm + ')';
XvtrLbl[i].SetBounds(X, Y + 24 + r * CAL_ROW_H + 5, CAL_SM_LBL_W, 18);
XvtrEd[i].SetBounds(X + CAL_SM_LBL_W, Y + 24 + r * CAL_ROW_H,
CAL_ED_W, BTN_H + 2);
Inc(Slot);
end;
XvtrLbl[i].Visible := FCalXvtrOn[i];
XvtrEd[i].Visible := FCalXvtrOn[i];
end;
XvtrRows := (Slot + CAL_COLS - 1) div CAL_COLS;
XvtrHdr.Visible := Slot > 0;
if Slot > 0 then Result := Y + 24 + XvtrRows * CAL_ROW_H + 12
else Result := Y + 12;
end;
procedure TSettingsForm.OnCalDispFromSMClick(Sender: TObject);
// Стартовая точка калибровки дисплея: те же цифры, что у стрелки,
// дальше доводятся руками по шумовой полке/эталонному генератору.
var i: Integer;
begin
FLoading := True;
try
for i := 0 to CFG_BAND_COUNT - 1 do
if (FEdDispBand[i] <> nil) and (FEdSMBand[i] <> nil) then
FEdDispBand[i].Value := FEdSMBand[i].Value;
for i := 0 to CFG_XVTR_COUNT - 1 do
if (FEdDispXvtr[i] <> nil) and (FEdSMXvtr[i] <> nil) then
FEdDispXvtr[i].Value := FEdSMXvtr[i].Value;
finally
FLoading := False;
end;
OnCalAnyChange(FBtnDispFromSM);
end;
procedure TSettingsForm.SetCalibrationBackend(IsPluto: Boolean);
@@ -2177,11 +2283,15 @@ begin
FCalConfig.PwrDetModel := FCmbPwrDetModel.ItemIndex;
for i := 0 to CFG_BAND_COUNT - 1 do
begin
if FEdPADetCal[i] <> nil then FCalConfig.PADetCal[i] := FEdPADetCal[i].Value;
if FEdSMBand[i] <> nil then FCalConfig.SMeterBandDB[i] := FEdSMBand[i].Value;
if FEdPADetCal[i] <> nil then FCalConfig.PADetCal[i] := FEdPADetCal[i].Value;
if FEdSMBand[i] <> nil then FCalConfig.SMeterBandDB[i] := FEdSMBand[i].Value;
if FEdDispBand[i] <> nil then FCalConfig.DispBandDB[i] := FEdDispBand[i].Value;
end;
for i := 0 to CFG_XVTR_COUNT - 1 do
if FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
begin
if FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
if FEdDispXvtr[i] <> nil then FCalConfig.DispXvtrDB[i] := FEdDispXvtr[i].Value;
end;
FCalConfig.SupplyVoltCal := FEdSupplyVoltCal.Value;
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
@@ -2226,9 +2336,13 @@ begin
begin
if FEdPADetCal[i] <> nil then FEdPADetCal[i].Value := C.PADetCal[i];
if FEdSMBand[i] <> nil then FEdSMBand[i].Value := C.SMeterBandDB[i];
if FEdDispBand[i] <> nil then FEdDispBand[i].Value := C.DispBandDB[i];
end;
for i := 0 to CFG_XVTR_COUNT - 1 do
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
begin
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
if FEdDispXvtr[i] <> nil then FEdDispXvtr[i].Value := C.DispXvtrDB[i];
end;
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
FEdAmpVoff.Value := C.AmpVoffMV;
FEdAmpSens.Value := C.AmpSensMVA;
+28 -2
View File
@@ -191,6 +191,11 @@ type
// SliceId — логический id (из RadioController), не WDSP-канал.
TOnSliceAudio = procedure(SliceId: Integer; const Left, Right: array of Single;
Count: Integer) of object;
// Калибровочный офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный,
// 1..MAX_PANS-1 = доп. панадаптер. Спрашивается раз в кадр из display-потока:
// величина зависит от диапазона/трансвертера, о которых знает только
// TRadioController (аналог SMeterCalFor для стрелки).
TOnDispCalQuery = function(PanId: Integer): Double of object;
// Пиксели спектра/водопада доп. пана — из display-потока (как FOnSpectrum/
// FOnWaterfall главного). Внутри колбэка НЕ звать slice/pan API движка.
TOnPanPixels = procedure(PanId: Integer; const Pixels: array of Single;
@@ -331,6 +336,7 @@ type
FOnDemodAudio:TOnAudioReady; // pre-volume/mute tap for digital decoders
FOnSpectrum: TOnSpectrumReady;
FOnWaterfall: TOnWaterfallReady;
FOnDispCal: TOnDispCalQuery;
FOnTXIQ: TOnTXIQReady;
// --- Мультислайсы ---
@@ -756,6 +762,8 @@ type
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
property OnSliceDemodAudio: TOnSliceAudio
read FOnSliceDemodAudio write FOnSliceDemodAudio;
// Офсет дисплея (дБ) на кадр — см. TOnDispCalQuery. nil = без коррекции.
property OnDispCal: TOnDispCalQuery read FOnDispCal write FOnDispCal;
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
// Пиксели доп. панов (display-поток). UI вешает при появлении панов (3.3).
@@ -3810,18 +3818,24 @@ var
Flag: Integer;
i, N, p: Integer;
DispID: Integer;
Cal: Single;
GotSpec, GotWf: Boolean;
begin
if not FInitialized or not FAnalyzerOpen then Exit;
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
// Калибровка дисплея (дБ) — сдвиг всей трассы, спрашиваем раз в кадр.
// На TX-дисплее коррекция входного тракта не при чём.
if Assigned(FOnDispCal) and (DispID = RX_DISP_ID) then Cal := FOnDispCal(0)
else Cal := 0;
Flag := 0;
GetPixels(DispID, 0, @PixBuf[0], @Flag);
if Flag <> 0 then
begin
for i := 0 to N - 1 do
FSpectrumPixels[i] := PixBuf[i];
FSpectrumPixels[i] := PixBuf[i] + Cal;
if Assigned(FOnSpectrum) then
FOnSpectrum(FSpectrumPixels, N);
end;
@@ -3831,7 +3845,7 @@ begin
if Flag <> 0 then
begin
for i := 0 to N - 1 do
FWaterfallPixels[i] := PixBuf[i];
FWaterfallPixels[i] := PixBuf[i] + Cal;
if Assigned(FOnWaterfall) then
FOnWaterfall(FWaterfallPixels, N);
end;
@@ -3858,6 +3872,18 @@ begin
finally
FSliceLock.Leave;
end;
// Пан может стоять на своём диапазоне — офсет спрашиваем по его PanId.
if GotSpec or GotWf then
begin
if Assigned(FOnDispCal) then Cal := FOnDispCal(p) else Cal := 0;
if Cal <> 0 then
begin
if GotSpec then
for i := 0 to N - 1 do PixBuf[i] := PixBuf[i] + Cal;
if GotWf then
for i := 0 to N - 1 do WfBuf[i] := WfBuf[i] + Cal;
end;
end;
if GotSpec and Assigned(FOnPanSpectrum) then
FOnPanSpectrum(p, PixBuf, N);
if GotWf and Assigned(FOnPanWaterfall) then