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; end;
// Маркер 2TON/IMD-измерения на спектре: кружок на пике продукта + подпись. // Маркер 2TON/IMD-измерения на спектре: кружок на пике продукта + подпись.
// Тона подписываются уровнем шкалы (dB), IMD-продукты — в dBc от среднего // Все маркеры подписываются абсолютным уровнем шкалы (dBm); относительная
// уровня тонов. // величина IMD3 в dBc — в сводке слева от полосы фильтра.
TIMDMarker = record TIMDMarker = record
FreqHz: Double; // абсолютная частота продукта FreqHz: Double; // абсолютная частота продукта
LevelDB: Double; // измеренный пик (шкала дисплея) LevelDB: Double; // измеренный пик (шкала дисплея)
@@ -741,11 +741,10 @@ begin
M[i].LevelDB := PeakDB(M[i].FreqHz); M[i].LevelDB := PeakDB(M[i].FreqHz);
ToneAvg := (M[0].LevelDB + M[1].LevelDB) * 0.5; ToneAvg := (M[0].LevelDB + M[1].LevelDB) * 0.5;
M[0].Caption := Format('%.0f', [M[0].LevelDB]); // Все маркеры — абсолютный уровень шкалы (dBm, как в Thetis); относительная
M[1].Caption := Format('%.0f', [M[1].LevelDB]); // величина в dBc есть в сводке FIMDSummary, чтобы не путать единицы.
// IMD-продукты — в dBc относительно среднего уровня тонов for i := 0 to 3 do
M[2].Caption := Format('%.0fc', [M[2].LevelDB - ToneAvg]); M[i].Caption := Format('%.0f', [M[i].LevelDB]);
M[3].Caption := Format('%.0fc', [M[3].LevelDB - ToneAvg]);
// Сводка только когда оба тона и хотя бы один продукт в окне буфера // Сводка только когда оба тона и хотя бы один продукт в окне буфера
if (M[0].LevelDB > -500) and (M[1].LevelDB > -500) and if (M[0].LevelDB > -500) and (M[1].LevelDB > -500) and
(Max(M[2].LevelDB, M[3].LevelDB) > -500) then (Max(M[2].LevelDB, M[3].LevelDB) > -500) then
@@ -782,7 +781,7 @@ const
CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3 CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3
var var
M: array[0..3] of TIMDMarker; M: array[0..3] of TIMDMarker;
N, i, X, Y, TY: Integer; N, i, X, Y, TY, TW, BX1, BX2, BVX: Integer;
Col: TColor; Col: TColor;
begin begin
if FSpanHz <= 0 then Exit; if FSpanHz <= 0 then Exit;
@@ -794,20 +793,27 @@ begin
X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W); X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
if (X < 0) or (X >= W) then Continue; if (X < 0) or (X >= W) then Continue;
Y := Max(0, Min(H - 1, Round((DBmax - M[i].LevelDB) * InvRange * H))); 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); RawCircle(X, Y, 4, Col);
// Подпись правее-выше кружка; у верхней кромки — вниз, у правой — влево. // Подпись по центру над своим кружком; у верхней кромки — под ним.
TY := Y - 14; TW := RawTextWidth(M[i].Caption, 7, True);
if TY < 2 then TY := Y + 6; TY := Y - 16;
if X + 6 + RawTextWidth(M[i].Caption, 7, True) < W then if TY < 2 then TY := Y + 8;
RawText(X + 6, TY, M[i].Caption, Col, 7, True, clNone) RawText(Max(2, Min(W - TW - 2, X - TW div 2)), TY,
else M[i].Caption, Col, 7, True, clNone);
RawText(X - 6 - RawTextWidth(M[i].Caption, 7, True), TY,
M[i].Caption, Col, 7, True, clNone);
end; end;
// Сводка в левом верхнем углу // Сводка слева от полосы фильтра, чтобы не прятаться за SampleRateOverlay
// в левом верхнем углу и сразу бросаться в глаза рядом с сигналом.
if FIMDSummary <> '' then 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; end;
// ════════════════════════════════════════════════════════════════════════════ // ════════════════════════════════════════════════════════════════════════════
+16 -9
View File
@@ -842,7 +842,7 @@ const
CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3 CLR_IMD = TColor($000A8CFF); // оранжевый — продукты IMD3
var var
M: array[0..3] of TIMDMarker; 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; Col: TColor;
begin begin
if FSpanHz <= 0 then Exit; if FSpanHz <= 0 then Exit;
@@ -854,28 +854,35 @@ begin
X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W); X := Round((M[i].FreqHz - (FCenterFreq - FSpanHz / 2)) / FSpanHz * W);
if (X < 0) or (X >= W) then Continue; if (X < 0) or (X >= W) then Continue;
Y := Max(0, Min(H - 1, Round((DBmax - M[i].LevelDB) * InvRange * H))); 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); DrawCircleGL(X, Y, 4.0, Col);
if (M[i].Caption <> FIMDLastText[i]) or FIMDLabelTex[i].Dirty then if (M[i].Caption <> FIMDLastText[i]) or FIMDLabelTex[i].Dirty then
begin begin
UploadText(FIMDLabelTex[i], M[i].Caption, Col, 7, True); UploadText(FIMDLabelTex[i], M[i].Caption, Col, 7, True);
FIMDLastText[i] := M[i].Caption; FIMDLastText[i] := M[i].Caption;
end; end;
TY := Y - 14; // Подпись по центру над своим кружком; у верхней кромки — под ним.
if TY < 2 then TY := Y + 6; TY := Y - 16;
TX := X + 6; if TY < 2 then TY := Y + 8;
if TX + FIMDLabelTex[i].W >= W then TX := X - 6 - FIMDLabelTex[i].W; TX := Max(2, Min(W - FIMDLabelTex[i].W - 2, X - FIMDLabelTex[i].W div 2));
DrawTexture(FIMDLabelTex[i], TX, TY); DrawTexture(FIMDLabelTex[i], TX, TY);
end; end;
// Сводка в левом верхнем углу // Сводка слева от полосы фильтра, чтобы не прятаться за SampleRateOverlay
// в левом верхнем углу и сразу бросаться в глаза рядом с сигналом.
if FIMDSummary <> '' then if FIMDSummary <> '' then
begin begin
if (FIMDSummary <> FIMDLastText[4]) or FIMDLabelTex[4].Dirty then if (FIMDSummary <> FIMDLastText[4]) or FIMDLabelTex[4].Dirty then
begin begin
UploadText(FIMDLabelTex[4], FIMDSummary, CLR_IMD, 9, True); UploadText(FIMDLabelTex[4], FIMDSummary, CLR_IMD, 11, True);
FIMDLastText[4] := FIMDSummary; FIMDLastText[4] := FIMDSummary;
end; 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;
end; end;