mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(slices): этап 3.2 — WDSPEngine per-pan (аккумулятор, анализаторы, слайсы в панах)
- общая IQ-очередь с TIQQueueItem.PanId (64→256 слотов); DSP-поток диспетчеризует пан-пакеты в PushPanItemToDSP: декод, feed анализатора PAN_DISP_BASE+PanId, аккумулятор, ProcessSlicesFor(PanId) под FSliceLock - пан-анализаторы: Create/DestroyPanAnalyzer + ApplyPanAnalyzerSettings (rate пана, без зума — пан-зум в 3.3+), живут в OpenAnalyzer/CloseAnalyzer, общие настройки дисплея перекатываются (ReapplyPanDisplaySettings) - слайсы в панах: TSliceChan.PanId, AddSlice(..., APanId), OpenSliceChannel от rate/блока своего пана, SliceDSPRateFor (WFM от rate пана); блок пана = FAudioBufSize*Rate/FAudioRate => аудио-блок слайсов неизменен - выдача пикселей: OnPanSpectrum/OnPanWaterfall(PanId,...) из display-потока, GetPixels пана под FSliceLock, колбэки вне лока - MAX_PANS=4 переехал в WDSPEngine (единый движок/контроллер/UI) - контроллер: AddPanDDC создаёт пан в движке до включения DDC, RemovePanDDC убирает слайсы (со звуковухами)+движок+DDC, гейт/NCO слайсов от центра своего пана, ретюн пана двигает его слайсы - смоук-тест на libwdsp без железа ALL PASS: пик спектра пана точно на месте, слайс пана выдал 1 кГц RMS 0.68, главный тракт жив, RemovePan чисто Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+62
-26
@@ -53,10 +53,8 @@ uses
|
||||
Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore;
|
||||
|
||||
const
|
||||
// Панадаптеры (этап 3): потолок панов = главный + до 3 доп. аппаратных DDC
|
||||
// (кап по Ethernet/CPU, см. doc/SLICES_PLAN.md 3.1). Фактический лимит
|
||||
// бэкенда — Caps.MaxPans (Pluto = 1).
|
||||
MAX_PANS = 4;
|
||||
// Панадаптеры (этап 3): потолок MAX_PANS живёт в WDSPEngine (общий для
|
||||
// движка/контроллера/UI). Фактический лимит бэкенда — Caps.MaxPans (Pluto=1).
|
||||
// Допустимые rate доп. пан-DDC, кГц (дефолт 384).
|
||||
PAN_DDC_RATES: array[0..2] of Word = (192, 384, 768);
|
||||
|
||||
@@ -136,6 +134,7 @@ type
|
||||
Active: Boolean;
|
||||
Id: Integer; // = id слайса в TWDSPEngine
|
||||
Letter: Char; // буква флага ('A'..'F'), пока для UI
|
||||
PanId: Integer; // пан слайса: 0 = главный, 1.. = доп. DDC-пан
|
||||
TargetHz: Double; // абсолютная частота слайса
|
||||
Mode: Integer;
|
||||
FilterLo: Integer;
|
||||
@@ -166,9 +165,10 @@ type
|
||||
function FindSliceIndex(Id: Integer): Integer; // -1 если нет
|
||||
procedure OnSliceAudioReady(SliceId: Integer;
|
||||
const Left, Right: array of Single; Count: Integer);
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz всех слайсов при смене центра
|
||||
// --- Панадаптеры на аппаратных DDC (этап 3.1) ---
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz слайсов пана 0 при смене центра
|
||||
// --- Панадаптеры на аппаратных DDC (этап 3.1/3.2) ---
|
||||
procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст
|
||||
function PanCenterHz(PanId: Integer): Double; // центр пана (0 = FCenterFreq)
|
||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||
procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state)
|
||||
@@ -534,10 +534,12 @@ type
|
||||
// ---- Мультислайсы (софт-fan-out) ----
|
||||
// Создаёт слайс на TargetHz: заводит WDSP-канал в движке + свой аудио-выход
|
||||
// на устройстве DevIndex (-1=default). Возвращает Id слайса или -1 при отказе
|
||||
// (нет слотов / TargetHz вне захваченной полосы). DevName для персиста/UI.
|
||||
// (нет слотов / TargetHz вне полосы пана). DevName для персиста/UI.
|
||||
// APanId>0 — слайс внутри доп. DDC-пана (тот должен быть создан AddPanDDC).
|
||||
function AddSlice(TargetHz: Double; Mode, FilterLo, FilterHi: Integer;
|
||||
AGC: TWDSPAGCMode; Vol: Double;
|
||||
DevIndex: Integer; const DevName: string): Integer;
|
||||
DevIndex: Integer; const DevName: string;
|
||||
APanId: Integer = 0): Integer;
|
||||
procedure RemoveSlice(Id: Integer);
|
||||
procedure SetSliceTarget(Id: Integer; TargetHz: Double);
|
||||
procedure SetSliceMode(Id, Mode: Integer);
|
||||
@@ -554,9 +556,9 @@ type
|
||||
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
||||
function SliceCount: Integer;
|
||||
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
||||
// Диапазон захваченной полосы вокруг центра, куда можно ставить софт-слайс.
|
||||
procedure CapturedSpan(out LoHz, HiHz: Double);
|
||||
function SliceFitsCapture(TargetHz: Double): Boolean;
|
||||
// Диапазон захваченной полосы пана (0 = главный), куда можно ставить слайс.
|
||||
procedure CapturedSpan(out LoHz, HiHz: Double; PanId: Integer = 0);
|
||||
function SliceFitsCapture(TargetHz: Double; PanId: Integer = 0): Boolean;
|
||||
|
||||
// ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ----
|
||||
// Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр (сырые Гц
|
||||
@@ -1128,27 +1130,41 @@ begin
|
||||
end;
|
||||
|
||||
procedure TRadioController.RefreshSliceShifts;
|
||||
// Смена центра ГЛАВНОГО захвата: пересчитываем только слайсы пана 0 —
|
||||
// слайсы доп. панов привязаны к центрам своих DDC (SetPanDDCFreq).
|
||||
var i: Integer;
|
||||
begin
|
||||
if not Assigned(FDSPEngine) then Exit;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active then
|
||||
if FSlices[i].Active and (FSlices[i].PanId = 0) then
|
||||
FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FCenterFreq);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CapturedSpan(out LoHz, HiHz: Double);
|
||||
// Полезная полоса ±47.5% от захвата (запас на краях фильтра/анти-алиасинга).
|
||||
var Half: Double;
|
||||
function TRadioController.PanCenterHz(PanId: Integer): Double;
|
||||
begin
|
||||
Half := FSampleRate * 0.475;
|
||||
LoHz := FCenterFreq - Half;
|
||||
HiHz := FCenterFreq + Half;
|
||||
if (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0) then
|
||||
Result := FPanFreqHz[PanId]
|
||||
else
|
||||
Result := FCenterFreq;
|
||||
end;
|
||||
|
||||
function TRadioController.SliceFitsCapture(TargetHz: Double): Boolean;
|
||||
procedure TRadioController.CapturedSpan(out LoHz, HiHz: Double; PanId: Integer);
|
||||
// Полезная полоса ±47.5% от захвата (запас на краях фильтра/анти-алиасинга).
|
||||
var Half, Rate: Double;
|
||||
begin
|
||||
if (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0) then
|
||||
Rate := FPanRateKHz[PanId] * 1000.0
|
||||
else
|
||||
Rate := FSampleRate;
|
||||
Half := Rate * 0.475;
|
||||
LoHz := PanCenterHz(PanId) - Half;
|
||||
HiHz := PanCenterHz(PanId) + Half;
|
||||
end;
|
||||
|
||||
function TRadioController.SliceFitsCapture(TargetHz: Double; PanId: Integer): Boolean;
|
||||
var Lo, Hi: Double;
|
||||
begin
|
||||
CapturedSpan(Lo, Hi);
|
||||
CapturedSpan(Lo, Hi, PanId);
|
||||
Result := (TargetHz >= Lo) and (TargetHz <= Hi);
|
||||
end;
|
||||
|
||||
@@ -1191,6 +1207,11 @@ begin
|
||||
if (i < MAX_DDCS) and (FDDCPanMap[i] < 0) then begin ddc := i; Break; end;
|
||||
if ddc < 0 then Exit;
|
||||
|
||||
// Движок: аккумулятор + analyzer пана (до включения DDC — к приходу первого
|
||||
// пакета пан уже готов; PushPanIQ без пана просто дропает).
|
||||
if FWDSPReady and not FDSPEngine.AddPan(PanId, Integer(RateKHz) * 1000) then
|
||||
Exit;
|
||||
|
||||
FPanDDCIdx[PanId] := ddc;
|
||||
FPanFreqHz[PanId] := FreqHz;
|
||||
FPanRateKHz[PanId] := RateKHz;
|
||||
@@ -1200,27 +1221,38 @@ begin
|
||||
end;
|
||||
|
||||
procedure TRadioController.RemovePanDDC(PanId: Integer);
|
||||
var ddc: Integer;
|
||||
var ddc, i: Integer;
|
||||
begin
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
ddc := FPanDDCIdx[PanId];
|
||||
if ddc < 0 then Exit;
|
||||
// Сначала снимаем роутинг (OnDDCIQ в сетевом потоке перестаёт брать пакеты
|
||||
// этого DDC), потом гасим DDC в железе.
|
||||
// этого DDC), потом слайсы пана (их звуковухи), потом движок и DDC в железе.
|
||||
FDDCPanMap[ddc] := -1;
|
||||
FPanDDCIdx[PanId] := -1;
|
||||
FPanFreqHz[PanId] := 0;
|
||||
FPanRateKHz[PanId] := 0;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].PanId = PanId) then
|
||||
RemoveSlice(FSlices[i].Id);
|
||||
if FWDSPReady then FDSPEngine.RemovePan(PanId);
|
||||
if Assigned(FNetwork) then FNetwork.SetPanDDC(ddc, False, 0, 0, 0);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetPanDDCFreq(PanId: Integer; FreqHz: Double);
|
||||
var i: Integer;
|
||||
begin
|
||||
if (PanId < 1) or (PanId >= MAX_PANS) then Exit;
|
||||
if FPanDDCIdx[PanId] < 0 then Exit;
|
||||
FPanFreqHz[PanId] := FreqHz;
|
||||
if Assigned(FNetwork) then
|
||||
FNetwork.SetPanDDCFreq(FPanDDCIdx[PanId], FreqHz);
|
||||
// Слайсы пана держат абсолютный TargetHz — обновляем их NCO-сдвиги
|
||||
// относительно нового центра (аналог RefreshSliceShifts главного).
|
||||
if Assigned(FDSPEngine) then
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].PanId = PanId) then
|
||||
FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FreqHz);
|
||||
end;
|
||||
|
||||
function TRadioController.PanDDCActive(PanId: Integer): Boolean;
|
||||
@@ -1242,14 +1274,16 @@ end;
|
||||
|
||||
function TRadioController.AddSlice(TargetHz: Double;
|
||||
Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double;
|
||||
DevIndex: Integer; const DevName: string): Integer;
|
||||
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
||||
var
|
||||
i, slot, Id: Integer;
|
||||
AO: TAudioOutput;
|
||||
begin
|
||||
Result := -1;
|
||||
if not Assigned(FDSPEngine) then Exit;
|
||||
if not SliceFitsCapture(TargetHz) then Exit; // вне захваченной полосы (Этап 3=HW DDC)
|
||||
if (APanId < 0) or (APanId >= MAX_PANS) then Exit;
|
||||
if (APanId > 0) and (FPanDDCIdx[APanId] < 0) then Exit; // пан не создан
|
||||
if not SliceFitsCapture(TargetHz, APanId) then Exit; // вне полосы пана
|
||||
|
||||
slot := -1;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
@@ -1258,7 +1292,8 @@ begin
|
||||
|
||||
Id := FNextSliceId;
|
||||
if not FDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi,
|
||||
TargetHz - FCenterFreq, Vol, AGC) then Exit;
|
||||
TargetHz - PanCenterHz(APanId), Vol, AGC,
|
||||
APanId) then Exit;
|
||||
Inc(FNextSliceId);
|
||||
|
||||
// Свой аудио-выход слайса на выбранной звуковухе.
|
||||
@@ -1272,6 +1307,7 @@ begin
|
||||
// Буква флага. Слайс A — это главный приёмник (не в FSlices[]), поэтому доп.
|
||||
// слайсы нумеруются с 'B' (slot 0 → B, 1 → C, …), иначе получалось два «A».
|
||||
FSlices[slot].Letter := Chr(Ord('B') + slot);
|
||||
FSlices[slot].PanId := APanId;
|
||||
FSlices[slot].TargetHz := TargetHz;
|
||||
FSlices[slot].Mode := Mode;
|
||||
FSlices[slot].FilterLo := FilterLo;
|
||||
@@ -1311,7 +1347,7 @@ begin
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].TargetHz := TargetHz;
|
||||
if Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetSliceShift(Id, TargetHz - FCenterFreq);
|
||||
FDSPEngine.SetSliceShift(Id, TargetHz - PanCenterHz(FSlices[idx].PanId));
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
||||
|
||||
Reference in New Issue
Block a user