diff --git a/RadioController.pas b/RadioController.pas index 39ee9fa..a5147c2 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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; diff --git a/Settings.pas b/Settings.pas index df2e668..bb96fda 100644 --- a/Settings.pas +++ b/Settings.pas @@ -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; diff --git a/SettingsForm.pas b/SettingsForm.pas index b842c9b..747aa29 100644 --- a/SettingsForm.pas +++ b/SettingsForm.pas @@ -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; diff --git a/WDSPEngine.pas b/WDSPEngine.pas index b966605..5e8de55 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -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