ui(2ton): читаемые IMD-маркеры на спектре

- сводка «IMD3 −NN dBc» слева от полосы фильтра (не прячется за
  SampleRateOverlay), шрифт 9→11
- маркеры пиков: все подписи в dBm как в Thetis (dBc только в сводке),
  все зелёные (оранжевый = вычисленная сводка), подпись центрирована
  над своим кружком (не наезжают у соседних тонов)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-18 09:59:50 +03:00
co-authored by Claude Fable 5
parent 7a999c95ee
commit e337f29314
2 changed files with 41 additions and 28 deletions
+25 -19
View File
@@ -37,8 +37,8 @@ type
end;
// Маркер 2TON/IMD-измерения на спектре: кружок на пике продукта + подпись.
// Тона подписываются уровнем шкалы (dB), IMD-продукты — в dBc от среднего
// уровня тонов.
// Все маркеры подписываются абсолютным уровнем шкалы (dBm); относительная
// величина IMD3 в dBc — в сводке слева от полосы фильтра.
TIMDMarker = record
FreqHz: Double; // абсолютная частота продукта
LevelDB: Double; // измеренный пик (шкала дисплея)
@@ -741,11 +741,10 @@ begin
M[i].LevelDB := PeakDB(M[i].FreqHz);
ToneAvg := (M[0].LevelDB + M[1].LevelDB) * 0.5;
M[0].Caption := Format('%.0f', [M[0].LevelDB]);
M[1].Caption := Format('%.0f', [M[1].LevelDB]);
// IMD-продукты — в dBc относительно среднего уровня тонов
M[2].Caption := Format('%.0fc', [M[2].LevelDB - ToneAvg]);
M[3].Caption := Format('%.0fc', [M[3].LevelDB - ToneAvg]);
// Все маркеры — абсолютный уровень шкалы (dBm, как в Thetis); относительная
// величина в dBc есть в сводке FIMDSummary, чтобы не путать единицы.
for i := 0 to 3 do
M[i].Caption := Format('%.0f', [M[i].LevelDB]);
// Сводка только когда оба тона и хотя бы один продукт в окне буфера
if (M[0].LevelDB > -500) and (M[1].LevelDB > -500) and
(Max(M[2].LevelDB, M[3].LevelDB) > -500) then
@@ -782,7 +781,7 @@ const
CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3
var
M: array[0..3] of TIMDMarker;
N, i, X, Y, TY: Integer;
N, i, X, Y, TY, TW, BX1, BX2, BVX: Integer;
Col: TColor;
begin
if FSpanHz <= 0 then Exit;
@@ -794,20 +793,27 @@ begin
X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
if (X < 0) or (X >= W) then Continue;
Y := Max(0, Min(H - 1, Round((DBmax - M[i].LevelDB) * InvRange * H)));
if M[i].IsTone then Col := CLR_TONE else Col := CLR_IMD;
// Все маркеры зелёные (dBm-замеры); оранжевый — только вычисленная
// сводка dBc, чтобы цвета не путались.
Col := CLR_TONE;
RawCircle(X, Y, 4, Col);
// Подпись правее-выше кружка; у верхней кромки — вниз, у правой — влево.
TY := Y - 14;
if TY < 2 then TY := Y + 6;
if X + 6 + RawTextWidth(M[i].Caption, 7, True) < W then
RawText(X + 6, TY, M[i].Caption, Col, 7, True, clNone)
else
RawText(X - 6 - RawTextWidth(M[i].Caption, 7, True), TY,
M[i].Caption, Col, 7, True, clNone);
// Подпись по центру над своим кружком; у верхней кромки — под ним.
TW := RawTextWidth(M[i].Caption, 7, True);
TY := Y - 16;
if TY < 2 then TY := Y + 8;
RawText(Max(2, Min(W - TW - 2, X - TW div 2)), TY,
M[i].Caption, Col, 7, True, clNone);
end;
// Сводка в левом верхнем углу
// Сводка слева от полосы фильтра, чтобы не прятаться за SampleRateOverlay
// в левом верхнем углу и сразу бросаться в глаза рядом с сигналом.
if FIMDSummary <> '' then
RawText(6, 6, FIMDSummary, CLR_IMD, 9, True, clNone);
begin
TW := RawTextWidth(FIMDSummary, 11, True);
if FActiveVfo = 0 then CalcFilterBandX(FVfoA, W, BX1, BX2, BVX)
else CalcFilterBandX(FVfoB, W, BX1, BX2, BVX);
X := Max(2, Min(W - TW - 2, BX1 - TW - 8));
RawText(X, 6, FIMDSummary, CLR_IMD, 11, True, clNone);
end;
end;
// ════════════════════════════════════════════════════════════════════════════
+16 -9
View File
@@ -842,7 +842,7 @@ const
CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3
var
M: array[0..3] of TIMDMarker;
N, i, X, Y, TX, TY: Integer;
N, i, X, Y, TX, TY, BX1, BX2, BVX: Integer;
Col: TColor;
begin
if FSpanHz <= 0 then Exit;
@@ -854,28 +854,35 @@ begin
X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
if (X < 0) or (X >= W) then Continue;
Y := Max(0, Min(H - 1, Round((DBmax - M[i].LevelDB) * InvRange * H)));
if M[i].IsTone then Col := CLR_TONE else Col := CLR_IMD;
// Все маркеры зелёные (dBm-замеры); оранжевый — только вычисленная
// сводка dBc, чтобы цвета не путались.
Col := CLR_TONE;
DrawCircleGL(X, Y, 4.0, Col);
if (M[i].Caption <> FIMDLastText[i]) or FIMDLabelTex[i].Dirty then
begin
UploadText(FIMDLabelTex[i], M[i].Caption, Col, 7, True);
FIMDLastText[i] := M[i].Caption;
end;
TY := Y - 14;
if TY < 2 then TY := Y + 6;
TX := X + 6;
if TX + FIMDLabelTex[i].W >= W then TX := X - 6 - FIMDLabelTex[i].W;
// Подпись по центру над своим кружком; у верхней кромки — под ним.
TY := Y - 16;
if TY < 2 then TY := Y + 8;
TX := Max(2, Min(W - FIMDLabelTex[i].W - 2, X - FIMDLabelTex[i].W div 2));
DrawTexture(FIMDLabelTex[i], TX, TY);
end;
// Сводка в левом верхнем углу
// Сводка слева от полосы фильтра, чтобы не прятаться за SampleRateOverlay
// в левом верхнем углу и сразу бросаться в глаза рядом с сигналом.
if FIMDSummary <> '' then
begin
if (FIMDSummary <> FIMDLastText[4]) or FIMDLabelTex[4].Dirty then
begin
UploadText(FIMDLabelTex[4], FIMDSummary, CLR_IMD, 9, True);
UploadText(FIMDLabelTex[4], FIMDSummary, CLR_IMD, 11, True);
FIMDLastText[4] := FIMDSummary;
end;
DrawTexture(FIMDLabelTex[4], 6, 6);
if FActiveVfo = 0 then CalcFilterBandX(FVfoA, W, BX1, BX2, BVX)
else CalcFilterBandX(FVfoB, W, BX1, BX2, BVX);
TX := Max(2, Min(W - FIMDLabelTex[4].W - 2,
BX1 - FIMDLabelTex[4].W - 8));
DrawTexture(FIMDLabelTex[4], TX, 6);
end;
end;