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;