mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 22:37:32 +00:00
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:
+31
-4
@@ -452,6 +452,10 @@ type
|
|||||||
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
procedure OnAudioReady(const Left, Right: array of Single; Count: Integer);
|
||||||
procedure OnDemodAudioReady(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);
|
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 OnSpectrumReady(const Pixels: array of Single; Count: Integer);
|
||||||
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
procedure OnWaterfallReady(const Pixels: array of Single; Count: Integer);
|
||||||
procedure OnMicPacket(const Data: TMicDataPacket);
|
procedure OnMicPacket(const Data: TMicDataPacket);
|
||||||
@@ -628,6 +632,9 @@ type
|
|||||||
function ReadSMeterDBm: Double;
|
function ReadSMeterDBm: Double;
|
||||||
// Офсет S-метра (дБ) для видимой частоты: слот трансвертера или диапазон.
|
// Офсет S-метра (дБ) для видимой частоты: слот трансвертера или диапазон.
|
||||||
function SMeterCalFor(VisibleHz: Double): Double;
|
function SMeterCalFor(VisibleHz: Double): Double;
|
||||||
|
// Офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный, 1..N — пан.
|
||||||
|
// Колбэк движка (display-поток), см. TOnDispCalQuery.
|
||||||
|
function DispCalFor(PanId: Integer): Double;
|
||||||
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
||||||
function SliceCount: Integer;
|
function SliceCount: Integer;
|
||||||
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
||||||
@@ -975,6 +982,9 @@ begin
|
|||||||
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
||||||
FDSPEngine.OnSliceDemodAudio := OnSliceDemodAudioReady;
|
FDSPEngine.OnSliceDemodAudio := OnSliceDemodAudioReady;
|
||||||
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
|
FDSPEngine.OnDemodAudio := OnDemodAudioReady;
|
||||||
|
// Калибровка уровня спектра/водопада на диапазон/трансвертер — движок
|
||||||
|
// спрашивает её раз в кадр (сам он про диапазоны не знает).
|
||||||
|
FDSPEngine.OnDispCal := DispCalFor;
|
||||||
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
FDSPEngine.SetDisplayResLimit(FDisplayPixels);
|
||||||
|
|
||||||
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
|
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
|
||||||
@@ -2060,7 +2070,8 @@ begin
|
|||||||
Result := FDSPEngine.GetSMeterDBm + SMeterCalFor(ActiveVfoHz);
|
Result := FDSPEngine.GetSMeterDBm + SMeterCalFor(ActiveVfoHz);
|
||||||
end;
|
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, а не КВ-вход трансивера.
|
// (не IF), и усиление тракта задаёт конвертер/LNB, а не КВ-вход трансивера.
|
||||||
// Индекс диапазона — по плану активного железа (HF openHPSDR / VHF-UHF Pluto).
|
// Индекс диапазона — по плану активного железа (HF openHPSDR / VHF-UHF Pluto).
|
||||||
@@ -2071,14 +2082,30 @@ begin
|
|||||||
and FXvtrSettings.Entries[FCurrentXvtr].Enabled
|
and FXvtrSettings.Entries[FCurrentXvtr].Enabled
|
||||||
and (VisibleHz >= FXvtrSettings.Entries[FCurrentXvtr].FreqBegin)
|
and (VisibleHz >= FXvtrSettings.Entries[FCurrentXvtr].FreqBegin)
|
||||||
and (VisibleHz <= FXvtrSettings.Entries[FCurrentXvtr].FreqEnd) then
|
and (VisibleHz <= FXvtrSettings.Entries[FCurrentXvtr].FreqEnd) then
|
||||||
Exit(FCal.SMeterXvtrDB[FCurrentXvtr]);
|
Exit(XvtrTbl[FCurrentXvtr]);
|
||||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
if FXvtrSettings.Entries[i].Enabled
|
if FXvtrSettings.Entries[i].Enabled
|
||||||
and (VisibleHz >= FXvtrSettings.Entries[i].FreqBegin)
|
and (VisibleHz >= FXvtrSettings.Entries[i].FreqBegin)
|
||||||
and (VisibleHz <= FXvtrSettings.Entries[i].FreqEnd) then
|
and (VisibleHz <= FXvtrSettings.Entries[i].FreqEnd) then
|
||||||
Exit(FCal.SMeterXvtrDB[i]);
|
Exit(XvtrTbl[i]);
|
||||||
Bi := FreqToBandIdx(VisibleHz, IsPluto);
|
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;
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
|
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
|
||||||
|
|||||||
@@ -345,6 +345,11 @@ type
|
|||||||
// Pluto); таблицы не пересекаются, т.к. секция JSON ключуется по MAC.
|
// Pluto); таблицы не пересекаются, т.к. секция JSON ключуется по MAC.
|
||||||
SMeterBandDB: array[0..CFG_BAND_COUNT-1] of Double;
|
SMeterBandDB: array[0..CFG_BAND_COUNT-1] of Double;
|
||||||
SMeterXvtrDB: array[0..CFG_XVTR_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;
|
end;
|
||||||
|
|
||||||
TGlobalSettings = record
|
TGlobalSettings = record
|
||||||
@@ -905,6 +910,13 @@ begin
|
|||||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
G.Cal.SMeterXvtrDB[i] :=
|
G.Cal.SMeterXvtrDB[i] :=
|
||||||
EnsureRange(JD(GObj,'cal_smeter_xvtr_'+IntToStr(i), SMLegacy), -30.0, 30.0);
|
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
|
for i := 0 to CFG_BAND_COUNT-1 do
|
||||||
begin
|
begin
|
||||||
@@ -1025,6 +1037,10 @@ begin
|
|||||||
JW(O,'cal_smeter_band_'+IntToStr(i),G.Cal.SMeterBandDB[i]);
|
JW(O,'cal_smeter_band_'+IntToStr(i),G.Cal.SMeterBandDB[i]);
|
||||||
for i := 0 to CFG_XVTR_COUNT-1 do
|
for i := 0 to CFG_XVTR_COUNT-1 do
|
||||||
JW(O,'cal_smeter_xvtr_'+IntToStr(i),G.Cal.SMeterXvtrDB[i]);
|
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;
|
end;
|
||||||
|
|
||||||
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
|
procedure TSettingsManager.SaveBand(const MAC: array of Byte; BandIdx: Integer;
|
||||||
|
|||||||
+163
-49
@@ -175,6 +175,14 @@ type
|
|||||||
FLblSMXvtr: array[0..CFG_XVTR_COUNT-1] of TLabel;
|
FLblSMXvtr: array[0..CFG_XVTR_COUNT-1] of TLabel;
|
||||||
FLblSMBandHdr: TLabel;
|
FLblSMBandHdr: TLabel;
|
||||||
FLblSMXvtrHdr: 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
|
FCalIsPluto: Boolean; // активное железо — AD936x
|
||||||
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
|
FCalXvtrOn: array[0..CFG_XVTR_COUNT-1] of Boolean; // слот трансвертера включён
|
||||||
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
|
FCalXvtrName: array[0..CFG_XVTR_COUNT-1] of string; // подпись слота
|
||||||
@@ -370,6 +378,15 @@ type
|
|||||||
procedure BuildPATab;
|
procedure BuildPATab;
|
||||||
procedure BuildCalibrationTab;
|
procedure BuildCalibrationTab;
|
||||||
procedure RelayoutCalTab;
|
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 CollectCalFromUI;
|
||||||
procedure OnCalAnyChange(Sender: TObject);
|
procedure OnCalAnyChange(Sender: TObject);
|
||||||
procedure BuildCATTab;
|
procedure BuildCATTab;
|
||||||
@@ -2066,6 +2083,53 @@ begin
|
|||||||
FEdSMXvtr[i].Tag := i;
|
FEdSMXvtr[i].Tag := i;
|
||||||
end;
|
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-план, трансвертеры скрыты)
|
RelayoutCalTab; // стартовая раскладка (HF-план, трансвертеры скрыты)
|
||||||
|
|
||||||
FLoading := False;
|
FLoading := False;
|
||||||
@@ -2077,21 +2141,19 @@ procedure TSettingsForm.RelayoutCalTab;
|
|||||||
// датчики питания — свойства Alex-моста openHPSDR, у AD936x такой телеметрии
|
// датчики питания — свойства Alex-моста openHPSDR, у AD936x такой телеметрии
|
||||||
// нет, поэтому обе группы прячутся, а S-метр поднимается наверх.
|
// нет, поэтому обе группы прячутся, а S-метр поднимается наверх.
|
||||||
var
|
var
|
||||||
i, r, c, X, Y, GTop, BandRows, XvtrRows, Slot, H: Integer;
|
i, r, c, X, Y, GTop, HSM, HDisp: Integer;
|
||||||
Nm: string;
|
Nm: string;
|
||||||
begin
|
begin
|
||||||
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
|
// Гард: вкладка ещё строится (или LoadXvtrSettings пришёл раньше неё).
|
||||||
if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
|
if (FCalGrpDet = nil) or (FCalGrpVA = nil) or (FCalGrpSM = nil)
|
||||||
or (FLblSMXvtrHdr = nil) then Exit;
|
or (FCalGrpDisp = nil) or (FLblDispXvtrHdr = nil) then Exit;
|
||||||
|
|
||||||
// ---- строки диапазонов: позиция по индексу плана, пустые слоты скрыты ----
|
// ---- подписи/видимость строк Det. Cal. (своя сетка, уже с подсказкой) ----
|
||||||
BandRows := 0;
|
|
||||||
for i := 0 to CFG_BAND_COUNT - 1 do
|
for i := 0 to CFG_BAND_COUNT - 1 do
|
||||||
begin
|
begin
|
||||||
Nm := BandName(i, FCalIsPluto);
|
Nm := BandName(i, FCalIsPluto);
|
||||||
r := i div CAL_COLS;
|
r := i div CAL_COLS;
|
||||||
c := i mod CAL_COLS;
|
c := i mod CAL_COLS;
|
||||||
|
|
||||||
X := CAL_PAD + c * (CAL_LBL_W + CAL_ED_W + 18);
|
X := CAL_PAD + c * (CAL_LBL_W + CAL_ED_W + 18);
|
||||||
Y := CAL_R1 + CAL_ROW_H + 46 + r * CAL_ROW_H;
|
Y := CAL_R1 + CAL_ROW_H + 46 + r * CAL_ROW_H;
|
||||||
if FLblPADetCal[i] <> nil then
|
if FLblPADetCal[i] <> nil then
|
||||||
@@ -2105,51 +2167,13 @@ begin
|
|||||||
FEdPADetCal[i].SetBounds(X + CAL_LBL_W, Y, CAL_ED_W, BTN_H + 2);
|
FEdPADetCal[i].SetBounds(X + CAL_LBL_W, Y, CAL_ED_W, BTN_H + 2);
|
||||||
FEdPADetCal[i].Visible := Nm <> '';
|
FEdPADetCal[i].Visible := Nm <> '';
|
||||||
end;
|
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;
|
end;
|
||||||
|
|
||||||
// ---- строки трансвертеров: только включённые слоты, без дырок ----
|
// ---- таблицы офсетов (S-метр и спектр) — одинаковая геометрия ----
|
||||||
Y := 52 + BandRows * CAL_ROW_H + 6;
|
HSM := LayoutCalOffsets(FLblSMBand, FEdSMBand, FLblSMXvtrHdr,
|
||||||
FLblSMXvtrHdr.SetBounds(CAL_PAD, Y, 300, 18);
|
FLblSMXvtr, FEdSMXvtr, 52);
|
||||||
Slot := 0;
|
HDisp := LayoutCalOffsets(FLblDispBand, FEdDispBand, FLblDispXvtrHdr,
|
||||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
FLblDispXvtr, FEdDispXvtr, 52);
|
||||||
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;
|
|
||||||
|
|
||||||
// ---- вертикальная укладка групп ----
|
// ---- вертикальная укладка групп ----
|
||||||
GTop := 86;
|
GTop := 86;
|
||||||
@@ -2162,7 +2186,89 @@ begin
|
|||||||
FCalGrpVA.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
|
FCalGrpVA.SetBounds(CAL_MARGIN, GTop, CAL_GRP_W, CAL_GRP2_H);
|
||||||
GTop := GTop + CAL_GRP2_H + 14;
|
GTop := GTop + CAL_GRP2_H + 14;
|
||||||
end;
|
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;
|
end;
|
||||||
|
|
||||||
procedure TSettingsForm.SetCalibrationBackend(IsPluto: Boolean);
|
procedure TSettingsForm.SetCalibrationBackend(IsPluto: Boolean);
|
||||||
@@ -2179,9 +2285,13 @@ begin
|
|||||||
begin
|
begin
|
||||||
if FEdPADetCal[i] <> nil then FCalConfig.PADetCal[i] := FEdPADetCal[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 FEdSMBand[i] <> nil then FCalConfig.SMeterBandDB[i] := FEdSMBand[i].Value;
|
||||||
|
if FEdDispBand[i] <> nil then FCalConfig.DispBandDB[i] := FEdDispBand[i].Value;
|
||||||
end;
|
end;
|
||||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||||
|
begin
|
||||||
if FEdSMXvtr[i] <> nil then FCalConfig.SMeterXvtrDB[i] := FEdSMXvtr[i].Value;
|
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.SupplyVoltCal := FEdSupplyVoltCal.Value;
|
||||||
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
|
FCalConfig.AmpVoffMV := FEdAmpVoff.Value;
|
||||||
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
|
FCalConfig.AmpSensMVA := FEdAmpSens.Value;
|
||||||
@@ -2226,9 +2336,13 @@ begin
|
|||||||
begin
|
begin
|
||||||
if FEdPADetCal[i] <> nil then FEdPADetCal[i].Value := C.PADetCal[i];
|
if FEdPADetCal[i] <> nil then FEdPADetCal[i].Value := C.PADetCal[i];
|
||||||
if FEdSMBand[i] <> nil then FEdSMBand[i].Value := C.SMeterBandDB[i];
|
if FEdSMBand[i] <> nil then FEdSMBand[i].Value := C.SMeterBandDB[i];
|
||||||
|
if FEdDispBand[i] <> nil then FEdDispBand[i].Value := C.DispBandDB[i];
|
||||||
end;
|
end;
|
||||||
for i := 0 to CFG_XVTR_COUNT - 1 do
|
for i := 0 to CFG_XVTR_COUNT - 1 do
|
||||||
|
begin
|
||||||
if FEdSMXvtr[i] <> nil then FEdSMXvtr[i].Value := C.SMeterXvtrDB[i];
|
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;
|
FEdSupplyVoltCal.Value := C.SupplyVoltCal;
|
||||||
FEdAmpVoff.Value := C.AmpVoffMV;
|
FEdAmpVoff.Value := C.AmpVoffMV;
|
||||||
FEdAmpSens.Value := C.AmpSensMVA;
|
FEdAmpSens.Value := C.AmpSensMVA;
|
||||||
|
|||||||
+28
-2
@@ -191,6 +191,11 @@ type
|
|||||||
// SliceId — логический id (из RadioController), не WDSP-канал.
|
// SliceId — логический id (из RadioController), не WDSP-канал.
|
||||||
TOnSliceAudio = procedure(SliceId: Integer; const Left, Right: array of Single;
|
TOnSliceAudio = procedure(SliceId: Integer; const Left, Right: array of Single;
|
||||||
Count: Integer) of object;
|
Count: Integer) of object;
|
||||||
|
// Калибровочный офсет спектра/водопада (дБ) для дисплея: PanId 0 = главный,
|
||||||
|
// 1..MAX_PANS-1 = доп. панадаптер. Спрашивается раз в кадр из display-потока:
|
||||||
|
// величина зависит от диапазона/трансвертера, о которых знает только
|
||||||
|
// TRadioController (аналог SMeterCalFor для стрелки).
|
||||||
|
TOnDispCalQuery = function(PanId: Integer): Double of object;
|
||||||
// Пиксели спектра/водопада доп. пана — из display-потока (как FOnSpectrum/
|
// Пиксели спектра/водопада доп. пана — из display-потока (как FOnSpectrum/
|
||||||
// FOnWaterfall главного). Внутри колбэка НЕ звать slice/pan API движка.
|
// FOnWaterfall главного). Внутри колбэка НЕ звать slice/pan API движка.
|
||||||
TOnPanPixels = procedure(PanId: Integer; const Pixels: array of Single;
|
TOnPanPixels = procedure(PanId: Integer; const Pixels: array of Single;
|
||||||
@@ -331,6 +336,7 @@ type
|
|||||||
FOnDemodAudio:TOnAudioReady; // pre-volume/mute tap for digital decoders
|
FOnDemodAudio:TOnAudioReady; // pre-volume/mute tap for digital decoders
|
||||||
FOnSpectrum: TOnSpectrumReady;
|
FOnSpectrum: TOnSpectrumReady;
|
||||||
FOnWaterfall: TOnWaterfallReady;
|
FOnWaterfall: TOnWaterfallReady;
|
||||||
|
FOnDispCal: TOnDispCalQuery;
|
||||||
FOnTXIQ: TOnTXIQReady;
|
FOnTXIQ: TOnTXIQReady;
|
||||||
|
|
||||||
// --- Мультислайсы ---
|
// --- Мультислайсы ---
|
||||||
@@ -756,6 +762,8 @@ type
|
|||||||
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
|
property OnSliceAudio: TOnSliceAudio read FOnSliceAudio write FOnSliceAudio;
|
||||||
property OnSliceDemodAudio: TOnSliceAudio
|
property OnSliceDemodAudio: TOnSliceAudio
|
||||||
read FOnSliceDemodAudio write FOnSliceDemodAudio;
|
read FOnSliceDemodAudio write FOnSliceDemodAudio;
|
||||||
|
// Офсет дисплея (дБ) на кадр — см. TOnDispCalQuery. nil = без коррекции.
|
||||||
|
property OnDispCal: TOnDispCalQuery read FOnDispCal write FOnDispCal;
|
||||||
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
property OnSpectrum: TOnSpectrumReady read FOnSpectrum write FOnSpectrum;
|
||||||
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
||||||
// Пиксели доп. панов (display-поток). UI вешает при появлении панов (3.3).
|
// Пиксели доп. панов (display-поток). UI вешает при появлении панов (3.3).
|
||||||
@@ -3810,18 +3818,24 @@ var
|
|||||||
Flag: Integer;
|
Flag: Integer;
|
||||||
i, N, p: Integer;
|
i, N, p: Integer;
|
||||||
DispID: Integer;
|
DispID: Integer;
|
||||||
|
Cal: Single;
|
||||||
GotSpec, GotWf: Boolean;
|
GotSpec, GotWf: Boolean;
|
||||||
begin
|
begin
|
||||||
if not FInitialized or not FAnalyzerOpen then Exit;
|
if not FInitialized or not FAnalyzerOpen then Exit;
|
||||||
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
||||||
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
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;
|
Flag := 0;
|
||||||
GetPixels(DispID, 0, @PixBuf[0], @Flag);
|
GetPixels(DispID, 0, @PixBuf[0], @Flag);
|
||||||
if Flag <> 0 then
|
if Flag <> 0 then
|
||||||
begin
|
begin
|
||||||
for i := 0 to N - 1 do
|
for i := 0 to N - 1 do
|
||||||
FSpectrumPixels[i] := PixBuf[i];
|
FSpectrumPixels[i] := PixBuf[i] + Cal;
|
||||||
if Assigned(FOnSpectrum) then
|
if Assigned(FOnSpectrum) then
|
||||||
FOnSpectrum(FSpectrumPixels, N);
|
FOnSpectrum(FSpectrumPixels, N);
|
||||||
end;
|
end;
|
||||||
@@ -3831,7 +3845,7 @@ begin
|
|||||||
if Flag <> 0 then
|
if Flag <> 0 then
|
||||||
begin
|
begin
|
||||||
for i := 0 to N - 1 do
|
for i := 0 to N - 1 do
|
||||||
FWaterfallPixels[i] := PixBuf[i];
|
FWaterfallPixels[i] := PixBuf[i] + Cal;
|
||||||
if Assigned(FOnWaterfall) then
|
if Assigned(FOnWaterfall) then
|
||||||
FOnWaterfall(FWaterfallPixels, N);
|
FOnWaterfall(FWaterfallPixels, N);
|
||||||
end;
|
end;
|
||||||
@@ -3858,6 +3872,18 @@ begin
|
|||||||
finally
|
finally
|
||||||
FSliceLock.Leave;
|
FSliceLock.Leave;
|
||||||
end;
|
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
|
if GotSpec and Assigned(FOnPanSpectrum) then
|
||||||
FOnPanSpectrum(p, PixBuf, N);
|
FOnPanSpectrum(p, PixBuf, N);
|
||||||
if GotWf and Assigned(FOnPanWaterfall) then
|
if GotWf and Assigned(FOnPanWaterfall) then
|
||||||
|
|||||||
Reference in New Issue
Block a user