mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +00:00
feat(slices): пан-зум панов N — свой бар/кнопки, span-clip анализатора пана
Движок: TPanChan.ZoomFactor/PanSlider, span-clip в ApplyPanAnalyzerSettings (математика главного, ZOOM_LIMIT 100), SetPanZoomPan с no-op guard (SetAnalyzer сбрасывает FFT-историю). Зум переживает смену rate/ADC пана. Контроллер: формула окна вынесена в ViewWindowFor (главный GetViewWindow тоже через неё), GetPanViewWindow (PanId=0 делегирует), PanZoomIn/Out по лестнице кратностей с якорем на центре окна, PanZoomDefault, геттеры. UI: TPanZoomEvent получил Sender; у панов N включён ряд зума, бар диспетчеризуется по объекту, кнопки −/⌂/+ общие через PanOfZoomButton (матч по идентичности — MakeFlatBtn создаёт кнопки с Owner=контейнер, PanFromSender их не видит); SyncPanZoomUI/RefreshPanZoom, вся мышь панов зум-корректна через GetPanViewWindow. Темизация баров панов N. Смоук panzoomtest ALL PASS (пик тона совпадает с формулой окна ±0.6%), подтверждено на экране. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+139
-10
@@ -167,6 +167,7 @@ type
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
||||
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||
procedure PanZoomStepTo(PanId: Integer; NewZ: Double); // зум с якорем на центре окна
|
||||
function PanCenterHz(PanId: Integer): Double; // центр пана (0 = FCenterFreq)
|
||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||
@@ -353,6 +354,8 @@ type
|
||||
FPanFreqHz: array[1..MAX_PANS-1] of Double;
|
||||
FPanRateKHz: array[1..MAX_PANS-1] of Word;
|
||||
FPanADC: array[1..MAX_PANS-1] of Byte; // ADC-источник пана (3.4)
|
||||
FPanZoomF: array[1..MAX_PANS-1] of Double; // зум пана 0..1 (0 = весь span)
|
||||
FPanPanSl: array[1..MAX_PANS-1] of Double; // положение окна пана 0..1
|
||||
FMainADCSrc: Byte; // ADC главного DDC (пан 0)
|
||||
FDDCPanMap: array[0..MAX_DDCS-1] of Integer;
|
||||
FLastDDCSeq: LongWord; // последний seq (из сетевого потока)
|
||||
@@ -581,6 +584,15 @@ type
|
||||
// N>0 = его пан-DDC (SetPanDDC). ADC ∈ [0..Caps.NumADCs-1].
|
||||
function PanADC(PanId: Integer): Byte;
|
||||
function SetPanADC(PanId: Integer; ADC: Byte): Boolean;
|
||||
// ---- Зум/пан доп. панов (PanId=0 делегирует главному зуму) ----
|
||||
// Видимое окно пана — та же формула, что span-clip его анализатора.
|
||||
procedure GetPanViewWindow(PanId: Integer; out ACenter, ASpan: Double);
|
||||
procedure SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||
procedure PanZoomIn(PanId: Integer); // шаг по лестнице, якорь = центр окна
|
||||
procedure PanZoomOut(PanId: Integer);
|
||||
procedure PanZoomDefault(PanId: Integer);
|
||||
function PanZoomFactor(PanId: Integer): Double;
|
||||
function PanPanSlider(PanId: Integer): Double;
|
||||
|
||||
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
||||
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
||||
@@ -1189,6 +1201,8 @@ begin
|
||||
FPanFreqHz[i] := 0;
|
||||
FPanRateKHz[i] := 0;
|
||||
FPanADC[i] := 0;
|
||||
FPanZoomF[i] := 0.0;
|
||||
FPanPanSl[i] := 0.5;
|
||||
end;
|
||||
for i := 0 to MAX_DDCS - 1 do
|
||||
FDDCPanMap[i] := -1;
|
||||
@@ -1235,6 +1249,8 @@ begin
|
||||
FPanFreqHz[PanId] := FreqHz;
|
||||
FPanRateKHz[PanId] := RateKHz;
|
||||
FPanADC[PanId] := ADCSource;
|
||||
FPanZoomF[PanId] := 0.0;
|
||||
FPanPanSl[PanId] := 0.5;
|
||||
FDDCPanMap[ddc] := PanId;
|
||||
FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource);
|
||||
Result := True;
|
||||
@@ -1282,6 +1298,8 @@ begin
|
||||
FPanFreqHz[PanId] := 0;
|
||||
FPanRateKHz[PanId] := 0;
|
||||
FPanADC[PanId] := 0;
|
||||
FPanZoomF[PanId] := 0.0;
|
||||
FPanPanSl[PanId] := 0.5;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].PanId = PanId) then
|
||||
RemoveSlice(FSlices[i].Id);
|
||||
@@ -3041,29 +3059,35 @@ begin Result := FZoomFactor; end;
|
||||
function TRadioController.PanSlider: Double;
|
||||
begin Result := FPanSlider; end;
|
||||
|
||||
procedure TRadioController.GetViewWindow(out ACenter: Double; out ASpan: Double);
|
||||
procedure ViewWindowFor(BaseC, BaseS, AZoom, APan: Double;
|
||||
out ACenter, ASpan: Double);
|
||||
// Видимое окно (центр+span, Гц) с учётом зума. Формула совпадает с расчётом
|
||||
// span-clip в WDSPEngine.ApplyRXAnalyzerSettings (bw=FSpanHz, clp=0), поэтому
|
||||
// оверлеи точно ложатся на пиксели. Без зума → базовые центр/span.
|
||||
// span-clip в WDSPEngine.Apply{RX,Pan}AnalyzerSettings (bw=BaseS, clp=0),
|
||||
// поэтому оверлеи точно ложатся на пиксели. Без зума → базовые центр/span.
|
||||
var
|
||||
FVis, OneMinusFVis, VisSpan, LowEdgeOff, CenterOff: Double;
|
||||
begin
|
||||
if (FZoomFactor <= 0.0) or (FSpanHz <= 0.0) then
|
||||
if (AZoom <= 0.0) or (BaseS <= 0.0) then
|
||||
begin
|
||||
ACenter := FCenterFreq;
|
||||
ASpan := FSpanHz;
|
||||
ACenter := BaseC;
|
||||
ASpan := BaseS;
|
||||
Exit;
|
||||
end;
|
||||
OneMinusFVis := 0.99 * Log10(9.0 * EnsureRange(FZoomFactor, 0.0, 1.0) + 1.0);
|
||||
OneMinusFVis := 0.99 * Log10(9.0 * EnsureRange(AZoom, 0.0, 1.0) + 1.0);
|
||||
FVis := 1.0 - OneMinusFVis; // доля span после зума
|
||||
if FVis < 1.0 / 100.0 then FVis := 1.0 / 100.0; // ZOOM_LIMIT
|
||||
VisSpan := FVis * FSpanHz;
|
||||
LowEdgeOff := -FSpanHz / 2.0 + EnsureRange(FPanSlider, 0.0, 1.0) * OneMinusFVis * FSpanHz;
|
||||
VisSpan := FVis * BaseS;
|
||||
LowEdgeOff := -BaseS / 2.0 + EnsureRange(APan, 0.0, 1.0) * OneMinusFVis * BaseS;
|
||||
CenterOff := LowEdgeOff + VisSpan / 2.0;
|
||||
ACenter := FCenterFreq + CenterOff;
|
||||
ACenter := BaseC + CenterOff;
|
||||
ASpan := VisSpan;
|
||||
end;
|
||||
|
||||
procedure TRadioController.GetViewWindow(out ACenter: Double; out ASpan: Double);
|
||||
begin
|
||||
ViewWindowFor(FCenterFreq, FSpanHz, FZoomFactor, FPanSlider, ACenter, ASpan);
|
||||
end;
|
||||
|
||||
function TRadioController.AnchoredPan(AZoom: Double): Double;
|
||||
// Положение пана, при котором видимое окно центрировано на маркере активного
|
||||
// VFO. Клампится в [0..1], чтобы окно не вылезало за span (у краёв маркер
|
||||
@@ -3156,6 +3180,111 @@ begin
|
||||
ApplyZoom(0.0, 0.5);
|
||||
end;
|
||||
|
||||
{ ---- Зум/пан доп. панов (PanId=0 делегирует главному) ---- }
|
||||
|
||||
procedure TRadioController.GetPanViewWindow(PanId: Integer;
|
||||
out ACenter, ASpan: Double);
|
||||
begin
|
||||
if PanId = 0 then
|
||||
begin
|
||||
GetViewWindow(ACenter, ASpan);
|
||||
Exit;
|
||||
end;
|
||||
ACenter := 0; ASpan := 0;
|
||||
if not PanDDCActive(PanId) then Exit;
|
||||
ViewWindowFor(FPanFreqHz[PanId], FPanRateKHz[PanId] * 1000.0,
|
||||
FPanZoomF[PanId], FPanPanSl[PanId], ACenter, ASpan);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetPanZoomPan(PanId: Integer; AZoom, APan: Double);
|
||||
// Зум пана — состояние сессии пана (не персистится с бандом, в отличие от
|
||||
// главного ApplyZoom): пан живёт до STOP.
|
||||
begin
|
||||
if PanId = 0 then
|
||||
begin
|
||||
ApplyZoom(AZoom, APan);
|
||||
Exit;
|
||||
end;
|
||||
if not PanDDCActive(PanId) then Exit;
|
||||
FPanZoomF[PanId] := EnsureRange(AZoom, 0.0, 1.0);
|
||||
FPanPanSl[PanId] := EnsureRange(APan, 0.0, 1.0);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetPanZoomPan(PanId, FPanZoomF[PanId], FPanPanSl[PanId]);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PanZoomStepTo(PanId: Integer; NewZ: Double);
|
||||
// Ставит новый зум, удерживая ЦЕНТР текущего видимого окна (у пана нет VFO-якоря
|
||||
// главного AnchoredPan; фиксация центра — естественный аналог).
|
||||
var
|
||||
C, S, BaseC, BaseS, U, OneMinus, FVis, NewPan: Double;
|
||||
begin
|
||||
if not PanDDCActive(PanId) then Exit;
|
||||
GetPanViewWindow(PanId, C, S);
|
||||
BaseC := FPanFreqHz[PanId];
|
||||
BaseS := FPanRateKHz[PanId] * 1000.0;
|
||||
if BaseS <= 0 then Exit;
|
||||
NewZ := EnsureRange(NewZ, 0.0, 1.0);
|
||||
// Инверсия ViewWindowFor: U = CenterOff/BaseS = −0.5 + Pan·OneMinus + FVis/2.
|
||||
U := (C - BaseC) / BaseS;
|
||||
OneMinus := 0.99 * Log10(9.0 * NewZ + 1.0);
|
||||
if OneMinus <= 1e-9 then
|
||||
NewPan := 0.5
|
||||
else
|
||||
begin
|
||||
FVis := 1.0 - OneMinus;
|
||||
if FVis < 1.0 / 100.0 then FVis := 1.0 / 100.0;
|
||||
NewPan := EnsureRange((U + 0.5 - FVis / 2.0) / OneMinus, 0.0, 1.0);
|
||||
end;
|
||||
SetPanZoomPan(PanId, NewZ, NewPan);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PanZoomIn(PanId: Integer);
|
||||
var CurMag: Double; i: Integer;
|
||||
begin
|
||||
if not PanDDCActive(PanId) then Exit;
|
||||
CurMag := ZoomZToMag(FPanZoomF[PanId]);
|
||||
for i := 0 to High(ZOOM_MAG_LADDER) do
|
||||
if ZOOM_MAG_LADDER[i] > CurMag * 1.001 then
|
||||
begin
|
||||
PanZoomStepTo(PanId, ZoomMagToZ(ZOOM_MAG_LADDER[i]));
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TRadioController.PanZoomOut(PanId: Integer);
|
||||
var CurMag: Double; i: Integer;
|
||||
begin
|
||||
if not PanDDCActive(PanId) then Exit;
|
||||
CurMag := ZoomZToMag(FPanZoomF[PanId]);
|
||||
for i := High(ZOOM_MAG_LADDER) downto 0 do
|
||||
if ZOOM_MAG_LADDER[i] < CurMag / 1.001 then
|
||||
begin
|
||||
if ZOOM_MAG_LADDER[i] <= 1.0 then SetPanZoomPan(PanId, 0.0, 0.5)
|
||||
else PanZoomStepTo(PanId, ZoomMagToZ(ZOOM_MAG_LADDER[i]));
|
||||
Exit;
|
||||
end;
|
||||
SetPanZoomPan(PanId, 0.0, 0.5);
|
||||
end;
|
||||
|
||||
procedure TRadioController.PanZoomDefault(PanId: Integer);
|
||||
begin
|
||||
SetPanZoomPan(PanId, 0.0, 0.5);
|
||||
end;
|
||||
|
||||
function TRadioController.PanZoomFactor(PanId: Integer): Double;
|
||||
begin
|
||||
if PanId = 0 then Result := FZoomFactor
|
||||
else if PanDDCActive(PanId) then Result := FPanZoomF[PanId]
|
||||
else Result := 0.0;
|
||||
end;
|
||||
|
||||
function TRadioController.PanPanSlider(PanId: Integer): Double;
|
||||
begin
|
||||
if PanId = 0 then Result := FPanSlider
|
||||
else if PanDDCActive(PanId) then Result := FPanPanSl[PanId]
|
||||
else Result := 0.5;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSampleRate(Hz: Integer);
|
||||
// Смена частоты дискретизации DDC. В этом приложении span ≡ sample rate.
|
||||
// Несёт: пересоздание WDSP-канала (Close+FlushQueue+Open), новый rate в
|
||||
|
||||
Reference in New Issue
Block a user