feat(slices): независимый rate панадаптеров (селектор в шапке)

- Создание пана: rate наследуется от главного с клампом ≤768к
  (вне сетки → 384к); за сменой rate главного паны не следуют
- Клик по «NNN kHz» в шапке пана N → меню полной сетки железа
  (PanRateAllowed = Caps.RatePresets ∩ кратные 48 кГц; захардкоженный
  PAN_DDC_RATES удалён)
- WDSPEngine.SetPanRate: под FSliceLock пересбор аккумулятора/анализатора
  пана + переоткрытие слайс-каналов на новом rate (state сохраняется)
- RadioController.SetPanDDCRate: движок → реконфиг DDC (SetPanDDC) →
  кламп слайсов к новой полосе; FPanADC[] хранится (база под 3.4)
- Шапка: rate отдельным лейблом (у панов N кликабелен, акцент/рука)

Смоук-тест без железа ALL PASS (тон: спектр+аудио слайса живы через
384→192→768). Проверено юзером на экране.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-11 23:28:25 +03:00
co-authored by Claude Fable 5
parent 95b4f749be
commit e5921b8196
5 changed files with 264 additions and 23 deletions
+80 -6
View File
@@ -184,6 +184,9 @@ type
FPanSplitMinTop: Integer;
FPanSplitMaxTop: Integer;
FPendingPanClose: Integer; // «×» шапки: PanId к закрытию в тике (0 = нет)
// Меню rate пана («NNN kHz» в шапке панов N; создаётся лениво).
FPanRateMenu: TPopupMenu;
FPanRateMenuPanId: Integer;
FSpecView: TSpectrumView;
FWidebandView: TWidebandView;
FUseOpenGLSpectrum: Boolean;
@@ -570,6 +573,8 @@ type
procedure DestroyAllExtraPans; // stop/выход
procedure UpdatePanHeaders; // текст+видимость шапок
procedure UpdateAddPanButton;
procedure OnPanRateClick(Sender: TObject); // «NNN kHz» шапки пана N
procedure PanRateMenuItemClick(Sender: TObject);
procedure AddSliceAtFreqPan(P: TPanafallPanel; Hz: Double);
procedure PanClickTune(P: TPanafallPanel; X, W: Integer); // клик по фону пана N
procedure PanViewWindow(P: TPanafallPanel; out VC, VS: Double);
@@ -2679,6 +2684,11 @@ begin
begin
FPans[i].HeaderPanel.Color := T.Panel;
FPans[i].HeaderLabel.Font.Color := T.Text;
// Rate-лейбл: кликабельный (паны N) подсвечиваем акцентом селектора.
if FPans[i].RateClickable then
FPans[i].HeaderRateLabel.Font.Color := T.BtnTextActive
else
FPans[i].HeaderRateLabel.Font.Color := T.Text;
end;
if i > 0 then
begin
@@ -5297,6 +5307,54 @@ begin
FActiveSliceId := Id;
end;
procedure TMainForm.OnPanRateClick(Sender: TObject);
// Клик по «NNN kHz» в шапке пана N: меню с полной сеткой rate бэкенда
// (фильтр PanRateAllowed — кратные 48 кГц). Текущий rate отмечен галкой.
var
P: TPanafallPanel;
MI: TMenuItem;
i, kHz: Integer;
begin
if not (Sender is TPanafallPanel) then Exit;
P := TPanafallPanel(Sender);
if (P.PanId < 1) or not FController.PanDDCActive(P.PanId) then Exit;
if FPanRateMenu = nil then
FPanRateMenu := TPopupMenu.Create(Self);
FPanRateMenu.Items.Clear;
for i := 0 to High(FController.BackendCaps.RatePresets) do
begin
kHz := FController.BackendCaps.RatePresets[i] div 1000;
if not FController.PanRateAllowed(Word(kHz)) then Continue;
MI := TMenuItem.Create(FPanRateMenu);
MI.Caption := Format('%d kHz', [kHz]);
MI.Tag := kHz;
MI.Checked := kHz = FController.PanDDCRateKHz(P.PanId);
MI.OnClick := PanRateMenuItemClick;
FPanRateMenu.Items.Add(MI);
end;
if FPanRateMenu.Items.Count = 0 then Exit;
FPanRateMenuPanId := P.PanId;
FPanRateMenu.PopUp; // в позиции курсора
end;
procedure TMainForm.PanRateMenuItemClick(Sender: TObject);
var
P: TPanafallPanel;
PanId: Integer;
begin
PanId := FPanRateMenuPanId;
FPanRateMenuPanId := 0;
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
P := FPans[PanId];
if P = nil then Exit;
if not FController.SetPanDDCRate(PanId, Word(TMenuItem(Sender).Tag)) then Exit;
// Вьюха/шапка/флаги под новый span (слайсы могли клампнуться к краям).
SyncPanViews;
P.PushAllSliceFlagStates;
P.LayoutFlags;
MarkPanDirty(P);
end;
procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer);
// Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог
// DoSpectrumClick главного: снап к 100 Гц). Цель — активный слайс, если он
@@ -5347,11 +5405,17 @@ begin
P.HeaderVisible := N > 1;
if P.BtnClosePan <> nil then P.BtnClosePan.Visible := (N > 1) and (i > 0);
if i = 0 then
P.SetHeaderText(Format('PAN 0 · %.6f MHz · %d kHz',
[FController.FCenterFreq / 1e6, FController.FSampleRate div 1000]))
begin
P.SetHeaderText(Format('PAN 0 · %.6f MHz ·',
[FController.FCenterFreq / 1e6]));
P.SetHeaderRate(Format('%d kHz', [FController.FSampleRate div 1000]));
end
else
P.SetHeaderText(Format('PAN %d · %.6f MHz · %d kHz',
[i, FController.PanDDCFreq(i) / 1e6, FController.PanDDCRateKHz(i)]));
begin
P.SetHeaderText(Format('PAN %d · %.6f MHz ·',
[i, FController.PanDDCFreq(i) / 1e6]));
P.SetHeaderRate(Format('%d kHz', [FController.PanDDCRateKHz(i)]));
end;
end;
end;
@@ -5370,6 +5434,7 @@ procedure TMainForm.OnAddPanClick(Sender: TObject);
// ADC 0; в центре автоматически создаётся первый слайс.
var
PanId, i: Integer;
RateKHz: Word;
Freq: Double;
P: TPanafallPanel;
Split: TPanel;
@@ -5380,8 +5445,15 @@ begin
if FPans[i] = nil then begin PanId := i; Break; end;
if PanId < 0 then Exit;
// Rate наследуется от главного пана с клампом сверху (768к — потолок доп.
// пана по Ethernet/CPU; 48/96/192к наследуются как есть). Не из сетки
// (напр. Pluto-подобные) — контроллер откатит на 384к. Дальше rate пана
// независим: селектор «NNN kHz» в шапке, за сменой rate главного НЕ следует.
RateKHz := FController.FSampleRate div 1000;
if RateKHz > 768 then RateKHz := 768;
Freq := ActiveVfoFreq;
if not FController.AddPanDDC(PanId, Freq, 384, 0) then Exit;
if not FController.AddPanDDC(PanId, Freq, RateKHz, 0) then Exit;
P := TPanafallPanel.Create(nil, FUseOpenGLSpectrum);
P.PanId := PanId;
@@ -5392,6 +5464,8 @@ begin
P.OnFlagsInvalidate := OnVfoOverlayInvalidate;
P.OnPanClose := OnPanHeaderClose;
P.OnSplitterMoved := RightPanelResize; // ratio обновлён → полный re-layout
P.OnRateClick := OnPanRateClick; // «NNN kHz» в шапке → меню rate
P.RateClickable := True;
P.SpectrumMouseDown := PanNSpectrumMouseDown;
P.SpectrumMouseMove := PanNSpectrumMouseMove;
P.SpectrumMouseUp := PanNSpectrumMouseUp;
@@ -5416,7 +5490,7 @@ begin
P.View.WfFrameInterval := EffectiveWfInterval;
P.View.PAMaxPower := FController.FPAMaxPower;
P.View.CenterFreq := Freq;
P.View.SpanHz := 384000;
P.View.SpanHz := FController.PanDDCRateKHz(PanId) * 1000.0;
// VFO/TX-маркеры главного на доп. пане не рисуем — уводим за пределы окна.
P.View.VfoA := -1e15;
P.View.VfoB := -1e15;