diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas index 583c7a2..c6042ec 100644 --- a/HPSDRNetwork.pas +++ b/HPSDRNetwork.pas @@ -140,6 +140,22 @@ type FCachedDUCValid: Boolean; FResendCount: Integer; + // Панадаптеры на доп. аппаратных DDC (этап 3): состояние per-DDC. + // Пишется из потока контроллера (SetPanDDC*), читается в SendFullHP / + // RebuildDDCSpecific — та же дисциплина, что у FCurrentRXFreq (torn read + // одного HP-кадра чинится следующим же кадром). + FPanDDCEnabled: array[0..MAX_DDCS-1] of Boolean; + FPanDDCFreqHz: array[0..MAX_DDCS-1] of Double; + FPanDDCRateKHz: array[0..MAX_DDCS-1] of Word; + FPanDDCADC: array[0..MAX_DDCS-1] of Byte; + // Кэш параметров главного DDC для RebuildDDCSpecific (пере-сборка DDC + // Specific при добавлении/удалении пан-DDC без повторного знания rate). + FMainNumDDCs: Byte; + FMainRateKHz: Word; + FMainADC: Byte; + FMainDither: Boolean; + FMainRandom: Boolean; + // Текущее состояние для построения HP пакетов FCurrentRXFreq: Double; FCurrentTXFreq: Double; @@ -192,6 +208,11 @@ type function DequeueDUCIQ(var Pkt: TDUCIQPacket): Boolean; function NextSeq(var S: LongWord): LongWord; procedure PackSeqBytes(var B: array of Byte; Seq: LongWord); + // Первый пользовательский DDC платы: 2 на ANGELIA/ORION/ORION2/SATURN + // (DDC0/1 зарезервированы PureSignal), иначе 0. + function DDCBaseIndex: Integer; + // Сборка+отправка DDC Specific из кэша главного DDC + активных пан-DDC. + procedure RebuildDDCSpecific; protected // Полиморфные геттеры состояния (свойства Connected/Running/Device/LastError/ @@ -219,6 +240,9 @@ type procedure SendDDCSpecific(const Pkt: TDDCSpecificPacket); override; procedure ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte = 0; DitherEnabled: Boolean = True; RandomEnabled: Boolean = True); override; + procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double; + RateKHz: Word; ADCSource: Byte); override; + procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); override; procedure SendDUCSpecific(const Pkt: TDUCSpecificPacket); override; procedure SendHighPriority(const Pkt: THighPriorityPacket); override; procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; @@ -665,6 +689,10 @@ begin Result.RatePresets[5] := 1536000; Result.MinFreqHz := 0; Result.MaxFreqHz := 61440000; + // Панадаптеры на аппаратных DDC: главный + до 3 доп. (кап по Ethernet/CPU, + // см. doc/SLICES_PLAN.md 3.1). Каждый DDC тюнится независимо. + Result.MaxPans := 4; + Result.IndependentPanFreq := True; end; constructor THPSDRNetwork.Create; @@ -1019,6 +1047,9 @@ begin FSeqHP := 0; FSeqAudio := 0; FSeqDUCIQ := 0; + // Пан-DDC не переживают сессию (контроллер гасит их в StopRunning; здесь — + // страховка от грязного завершения прошлой сессии). + FillChar(FPanDDCEnabled, SizeOf(FPanDDCEnabled), 0); ResetDUCIQQueue; StartThreads; @@ -1457,13 +1488,52 @@ begin if FConnected then SendFullHP; end; +function THPSDRNetwork.DDCBaseIndex: Integer; +begin + // Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal) + // Для HERMES/HL2 — с 0 + if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION2=5, SATURN=10 + Result := 2 + else + Result := 0; +end; + procedure THPSDRNetwork.ConfigureDDCs(NumDDCs: Byte; SampleRate: Word; ADCSource: Byte; DitherEnabled: Boolean; RandomEnabled: Boolean); +begin + // Кэш главного DDC — RebuildDDCSpecific пере-собирает пакет и при + // добавлении/удалении пан-DDC (SetPanDDC) без повторной передачи rate. + FMainNumDDCs := NumDDCs; + FMainRateKHz := SampleRate; + FMainADC := ADCSource; + FMainDither := DitherEnabled; + FMainRandom := RandomEnabled; + RebuildDDCSpecific; +end; + +procedure THPSDRNetwork.RebuildDDCSpecific; var Pkt: TDDCSpecificPacket; i, ddc: Integer; DDCBase: Integer; + + procedure AddDDC(ddc: Integer; RateKHz: Word; ADC: Byte); + begin + // Enable bit для DDC ddc + Pkt.DDCEnable[ddc div 8] := Pkt.DDCEnable[ddc div 8] or Byte(1 shl (ddc mod 8)); + // Config: 6 байт на DDC начиная с offset 17 в пакете → в массиве DDCConfig + // DDCConfig[ddc*6 + 0] = ADC source + // DDCConfig[ddc*6 + 1..2] = sample rate / 1000 (ksps) + // DDCConfig[ddc*6 + 5] = bits per sample + Pkt.DDCConfig[ddc * 6] := ADC; + Pkt.DDCConfig[ddc * 6 + 1] := (RateKHz shr 8) and $FF; + Pkt.DDCConfig[ddc * 6 + 2] := RateKHz and $FF; + Pkt.DDCConfig[ddc * 6 + 3] := 0; + Pkt.DDCConfig[ddc * 6 + 4] := 0; + Pkt.DDCConfig[ddc * 6 + 5] := 24; // 24 bits per sample + end; + begin FillChar(Pkt, SizeOf(Pkt), 0); @@ -1474,34 +1544,21 @@ begin Pkt.NumADCs := 1; // Dither/Random — биты 0..NumADCs-1 (один бит на ADC) - if DitherEnabled then + if FMainDither then Pkt.DitherADC := (1 shl Pkt.NumADCs) - 1; - if RandomEnabled then + if FMainRandom then Pkt.RandomADC := (1 shl Pkt.NumADCs) - 1; - // Для ANGELIA/ORION/ORION2/SATURN DDC начинается с индекса 2 (DDC0/1 — PureSignal) - // Для HERMES/HL2 — с 0 - if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION2=5, SATURN=10 - DDCBase := 2 - else - DDCBase := 0; + DDCBase := DDCBaseIndex; - for i := 0 to NumDDCs - 1 do - begin - ddc := DDCBase + i; - // Enable bit для DDC ddc - Pkt.DDCEnable[ddc div 8] := Pkt.DDCEnable[ddc div 8] or Byte(1 shl (ddc mod 8)); - // Config: 6 байт на DDC начиная с offset 17 в пакете → в массиве DDCConfig - // DDCConfig[ddc*6 + 0] = ADC source - // DDCConfig[ddc*6 + 1..2] = sample rate / 1000 (ksps) - // DDCConfig[ddc*6 + 5] = bits per sample - Pkt.DDCConfig[ddc * 6] := ADCSource; - Pkt.DDCConfig[ddc * 6 + 1] := (SampleRate shr 8) and $FF; - Pkt.DDCConfig[ddc * 6 + 2] := SampleRate and $FF; - Pkt.DDCConfig[ddc * 6 + 3] := 0; - Pkt.DDCConfig[ddc * 6 + 4] := 0; - Pkt.DDCConfig[ddc * 6 + 5] := 24; // 24 bits per sample - end; + for i := 0 to FMainNumDDCs - 1 do + AddDDC(DDCBase + i, FMainRateKHz, FMainADC); + + // Пан-DDC (этап 3): доп. панадаптеры на своих rate/ADC. Контроллер + // аллоцирует hw-индексы после главного, конфликтов с циклом выше нет. + for ddc := 0 to MAX_DDCS - 1 do + if FPanDDCEnabled[ddc] then + AddDDC(ddc, FPanDDCRateKHz[ddc], FPanDDCADC[ddc]); SendDDCSpecific(Pkt); FCachedDDCSpec := Pkt; @@ -1509,6 +1566,28 @@ begin FResendCount := 0; end; +procedure THPSDRNetwork.SetPanDDC(DDCIdx: Integer; Enabled: Boolean; + FreqHz: Double; RateKHz: Word; ADCSource: Byte); +begin + if (DDCIdx < 0) or (DDCIdx >= MAX_DDCS) then Exit; + FPanDDCEnabled[DDCIdx] := Enabled; + FPanDDCFreqHz[DDCIdx] := FreqHz; + FPanDDCRateKHz[DDCIdx] := RateKHz; + FPanDDCADC[DDCIdx] := ADCSource; + if FConnected and FRunning then + begin + RebuildDDCSpecific; // enable-бит + rate/ADC + SendFullHP; // частотное слово DDC + end; +end; + +procedure THPSDRNetwork.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); +begin + if (DDCIdx < 0) or (DDCIdx >= MAX_DDCS) then Exit; + FPanDDCFreqHz[DDCIdx] := FreqHz; + if FConnected and FPanDDCEnabled[DDCIdx] then SendFullHP; +end; + procedure THPSDRNetwork.SendDUCSpecific(const Pkt: TDUCSpecificPacket); var B: TDUCSpecificPacket; @@ -1557,6 +1636,7 @@ var Alex1: LongWord; IsOrion2: Boolean; DDCBase: Integer; // 0 для HERMES, 2 для ORION/ORION2/ANGELIA + ddc: Integer; begin if not FConnected then Exit; @@ -1580,13 +1660,9 @@ begin if FPTTActive then Buf[4] := Buf[4] or HP_PTT0; end; - // DDC base: ORION/ORION2/ANGELIA/SATURN начинают с DDC2 // IsOrion2: платы с Alex BPF и TxRx Status bit18 (ORION_MK2=5, SATURN=10) IsOrion2 := FDevice.BoardType in [5, 10]; // ORION_MK2=5, SATURN=10 - if FDevice.BoardType in [3, 4, 5, 10] then // ANGELIA=3, ORION=4, ORION_MK2=5, SATURN=10 - DDCBase := 2 - else - DDCBase := 0; + DDCBase := DDCBaseIndex; // DDC RX frequency (bytes 9 + DDC*4) Ph := FreqToPhaseWord(FCurrentRXFreq); @@ -1595,6 +1671,18 @@ begin Buf[11 + DDCBase*4] := (Ph shr 8) and $FF; Buf[12 + DDCBase*4] := Ph and $FF; + // Частоты пан-DDC (этап 3): своё частотное слово каждому активному DDC. + // Макс. смещение 9+79*4=325 < 329 (DUC) — в частотную зону влезают все. + for ddc := 0 to MAX_DDCS - 1 do + if FPanDDCEnabled[ddc] then + begin + Ph := FreqToPhaseWord(FPanDDCFreqHz[ddc]); + Buf[9 + ddc*4] := (Ph shr 24) and $FF; + Buf[10 + ddc*4] := (Ph shr 16) and $FF; + Buf[11 + ddc*4] := (Ph shr 8) and $FF; + Buf[12 + ddc*4] := Ph and $FF; + end; + // DUC TX frequency (bytes 329-332) Ph := FreqToPhaseWord(FCurrentTXFreq); Buf[329] := (Ph shr 24) and $FF; diff --git a/PlutoBackend.pas b/PlutoBackend.pas index c08cb89..d89e4f8 100644 --- a/PlutoBackend.pas +++ b/PlutoBackend.pas @@ -432,6 +432,10 @@ begin Result.RatePresets[7] := 5760000; Result.MinFreqHz := PLUTO_MIN_HZ; Result.MaxFreqHz := PLUTO_MAX_HZ; + // Один RX-тракт — только главный пан. (Pluto+ 2r2t = MaxPans 2 с общим + // RX LO, IndependentPanFreq=False — этап 3.6.) + Result.MaxPans := 1; + Result.IndependentPanFreq := False; end; function TPlutoBackend.ProbeURI(const AURI: string; out Dev: TRadioDevice): Boolean; diff --git a/RadioBackend.pas b/RadioBackend.pas index 569bdcc..df29644 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -43,6 +43,12 @@ type HasHWGain: Boolean; // manual gain / hw-AGC (Pluto) HasRFBandwidth: Boolean; // настройка аналоговой RF-полосы (Pluto) HasFullDuplex: Boolean; // одновременный RX+TX (Pluto / QO-100) + // Панадаптеры (этап 3): сколько всего панов может показать бэкенд + // (1 = только главный; HPSDR P2 = главный + доп. DDC). Паны N>0 живут + // на своих аппаратных DDC (SetPanDDC*). + MaxPans: Integer; + // False = частоты панов связаны с паном 0 (Pluto+ 2r2t: общий RX LO). + IndependentPanFreq: Boolean; MinSampleRate: Integer; RatePresets: TBackendRateArray; // пресеты для SampleRateOverlay MinFreqHz: Double; @@ -118,6 +124,14 @@ type DitherEnabled: Boolean = True; RandomEnabled: Boolean = True); virtual; abstract; + // ---- Панадаптеры на доп. аппаратных DDC (этап 3, только HPSDR) ---- + // SetPanDDC включает/выключает и конфигурирует DDC с hw-индексом DDCIdx + // (rate/ADC → DDC Specific, частота → High Priority). SetPanDDCFreq — + // только ретюн (дёшево, без реконфига DDC). База — no-op (Caps.MaxPans=1). + procedure SetPanDDC(DDCIdx: Integer; Enabled: Boolean; FreqHz: Double; + RateKHz: Word; ADCSource: Byte); virtual; + procedure SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); virtual; + // ---- Частота / drive / TX-состояние ---- procedure SetRunAndFreq(Run: Boolean; DDC0FreqHz, DUCFreqHz: Double; DriveLevel: Byte = 100); virtual; abstract; @@ -268,4 +282,13 @@ procedure TRadioBackend.SetTxAtten(Db: Double); begin end; +procedure TRadioBackend.SetPanDDC(DDCIdx: Integer; Enabled: Boolean; + FreqHz: Double; RateKHz: Word; ADCSource: Byte); +begin +end; + +procedure TRadioBackend.SetPanDDCFreq(DDCIdx: Integer; FreqHz: Double); +begin +end; + end. diff --git a/RadioController.pas b/RadioController.pas index 95b3d5a..43d539b 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -52,6 +52,14 @@ uses WDSPEngine, AudioOutput, AudioInput, BeaconDecoder, BeaconFEC, Settings, ChannelStore, FMRepeater, BoardUtils, DeviceStore; +const + // Панадаптеры (этап 3): потолок панов = главный + до 3 доп. аппаратных DDC + // (кап по Ethernet/CPU, см. doc/SLICES_PLAN.md 3.1). Фактический лимит + // бэкенда — Caps.MaxPans (Pluto = 1). + MAX_PANS = 4; + // Допустимые rate доп. пан-DDC, кГц (дефолт 384). + PAN_DDC_RATES: array[0..2] of Word = (192, 384, 768); + type // QO-100 beacon lock — статус контура (для UI/маркеров). TBeaconState = (bsIdle, // лок выключен @@ -159,6 +167,8 @@ type procedure OnSliceAudioReady(SliceId: Integer; const Left, Right: array of Single; Count: Integer); procedure RefreshSliceShifts; // пересчёт ShiftHz всех слайсов при смене центра + // --- Панадаптеры на аппаратных DDC (этап 3.1) --- + procedure ResetPanDDCMaps; // все пан-слоты свободны, DDC→Pan пуст procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг) procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state) @@ -336,6 +346,14 @@ type FRXStartTime: QWord; // GetTickCount64 момента START FRXLastPktTime: QWord; // GetTickCount64 последнего принятого пакета FActiveDDC: Integer; // DDC index для текущей платы (0 или 2) + // ---- Панадаптеры на аппаратных DDC (этап 3.1) ---- + // PanId 1..MAX_PANS-1 → hw DDC index (-1 = пан не создан); пан 0 = главный + // тракт (FActiveDDC). FDDCPanMap — обратный маппинг DDC→PanId для роутинга + // IQ в OnDDCIQ (пишется под FSliceLock-дисциплиной: только UI-поток). + FPanDDCIdx: array[1..MAX_PANS-1] of Integer; + FPanFreqHz: array[1..MAX_PANS-1] of Double; + FPanRateKHz: array[1..MAX_PANS-1] of Word; + FDDCPanMap: array[0..MAX_DDCS-1] of Integer; FLastDDCSeq: LongWord; // последний seq (из сетевого потока) FLastDDCIndex: Integer; // последний DDC index FDDCLastSeq: array[0..6] of LongWord; @@ -540,6 +558,18 @@ type procedure CapturedSpan(out LoHz, HiHz: Double); function SliceFitsCapture(TargetHz: Double): Boolean; + // ---- Панадаптеры на аппаратных DDC (этап 3.1; UI/движок — 3.2/3.3) ---- + // Создаёт пан PanId (1..MAX_PANS-1) на своём DDC: FreqHz = центр (сырые Гц + // железа, XVTR-трансляции у панов нет), RateKHz ∈ PAN_DDC_RATES (иначе 384), + // ADCSource — АЦП платы. False = нет слота/бэкенд не умеет/не Running. + function AddPanDDC(PanId: Integer; FreqHz: Double; RateKHz: Word = 384; + ADCSource: Byte = 0): Boolean; + procedure RemovePanDDC(PanId: Integer); + procedure SetPanDDCFreq(PanId: Integer; FreqHz: Double); // ретюн (HP-пакет) + function PanDDCActive(PanId: Integer): Boolean; + function PanDDCFreq(PanId: Integer): Double; + function PanDDCRateKHz(PanId: Integer): Word; + // Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на // 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain. function PlutoTxAttForDrive: Double; // дБ<0 под текущий FDrivePercent @@ -711,6 +741,7 @@ begin FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000; FNextSliceId := 1; // мультислайсы: генератор id FillChar(FSlices, SizeOf(FSlices), 0); + ResetPanDDCMaps; // паны: все слоты свободны FZoomFactor := 0.0; FPanSlider := 0.5; // 0.0 = весь span (без зума) FCurrentBand := 5; FCurrentXvtr := -1; FSpectrumBufCount := 1024; FWaterfallBufCount := 1024; @@ -871,13 +902,17 @@ begin FDDCSeqValid[DDCIndex] := True; end; - // Подаём IQ в DSP — только с активного DDC - if (DDCIndex = FActiveDDC) and FWDSPReady then + // Подаём IQ в DSP: активный DDC → главный тракт, пан-DDC → его пан (3.2). + if FWDSPReady then begin SamplesPerFrame := (Integer(Data.SamplesPerFrame[0]) shl 8) or Integer(Data.SamplesPerFrame[1]); if SamplesPerFrame <= 0 then SamplesPerFrame := 238; // fallback - FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame); + if DDCIndex = FActiveDDC then + FDSPEngine.PushDDCPacket(Data.IQData, 0, SamplesPerFrame) + else if (DDCIndex >= 0) and (DDCIndex < MAX_DDCS) + and (FDDCPanMap[DDCIndex] > 0) then + FDSPEngine.PushPanIQ(FDDCPanMap[DDCIndex], Data.IQData, 0, SamplesPerFrame); end; // Запоминаем последний seq — статус обновит таймер (без Synchronize в hot path) @@ -1117,6 +1152,94 @@ begin Result := (TargetHz >= Lo) and (TargetHz <= Hi); end; +{ ---- Панадаптеры на аппаратных DDC (этап 3.1) ---- } + +procedure TRadioController.ResetPanDDCMaps; +var i: Integer; +begin + for i := 1 to MAX_PANS - 1 do + begin + FPanDDCIdx[i] := -1; + FPanFreqHz[i] := 0; + FPanRateKHz[i] := 0; + end; + for i := 0 to MAX_DDCS - 1 do + FDDCPanMap[i] := -1; +end; + +function TRadioController.AddPanDDC(PanId: Integer; FreqHz: Double; + RateKHz: Word; ADCSource: Byte): Boolean; +var + i, ddc: Integer; + RateOk: Boolean; +begin + Result := False; + if (PanId < 1) or (PanId >= MAX_PANS) then Exit; + if FPanDDCIdx[PanId] >= 0 then Exit; // уже создан + if not (Assigned(FNetwork) and FRunning) then Exit; + if PanId >= FNetwork.Caps.MaxPans then Exit; // бэкенд не умеет (Pluto=1) + + RateOk := False; + for i := Low(PAN_DDC_RATES) to High(PAN_DDC_RATES) do + if PAN_DDC_RATES[i] = RateKHz then begin RateOk := True; Break; end; + if not RateOk then RateKHz := 384; + + // hw-индекс: следующий свободный после главного DDC (слот-стабильности не + // нужно — hw-индекс скрыт за PanId, наружу торчит только PanId). + ddc := -1; + for i := FActiveDDC + 1 to FActiveDDC + MAX_PANS - 1 do + if (i < MAX_DDCS) and (FDDCPanMap[i] < 0) then begin ddc := i; Break; end; + if ddc < 0 then Exit; + + FPanDDCIdx[PanId] := ddc; + FPanFreqHz[PanId] := FreqHz; + FPanRateKHz[PanId] := RateKHz; + FDDCPanMap[ddc] := PanId; + FNetwork.SetPanDDC(ddc, True, FreqHz, RateKHz, ADCSource); + Result := True; +end; + +procedure TRadioController.RemovePanDDC(PanId: Integer); +var ddc: Integer; +begin + if (PanId < 1) or (PanId >= MAX_PANS) then Exit; + ddc := FPanDDCIdx[PanId]; + if ddc < 0 then Exit; + // Сначала снимаем роутинг (OnDDCIQ в сетевом потоке перестаёт брать пакеты + // этого DDC), потом гасим DDC в железе. + FDDCPanMap[ddc] := -1; + FPanDDCIdx[PanId] := -1; + FPanFreqHz[PanId] := 0; + FPanRateKHz[PanId] := 0; + if Assigned(FNetwork) then FNetwork.SetPanDDC(ddc, False, 0, 0, 0); +end; + +procedure TRadioController.SetPanDDCFreq(PanId: Integer; FreqHz: Double); +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); +end; + +function TRadioController.PanDDCActive(PanId: Integer): Boolean; +begin + Result := (PanId >= 1) and (PanId < MAX_PANS) and (FPanDDCIdx[PanId] >= 0); +end; + +function TRadioController.PanDDCFreq(PanId: Integer): Double; +begin + if PanDDCActive(PanId) then Result := FPanFreqHz[PanId] + else Result := 0; +end; + +function TRadioController.PanDDCRateKHz(PanId: Integer): Word; +begin + if PanDDCActive(PanId) then Result := FPanRateKHz[PanId] + else Result := 0; +end; + function TRadioController.AddSlice(TargetHz: Double; Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double; DevIndex: Integer; const DevName: string): Integer; @@ -3409,10 +3532,13 @@ begin FNetwork.SendGeneralPacket(GenPkt); FNetwork.ConfigureWideband(0, FShowWideband, 512, 16, 70, 32); - if FNetwork.Device.BoardType in [3, 4, 5] then + // ANGELIA=3, ORION=4, ORION2=5, SATURN=10: DDC0/1 — PureSignal, юзерский с 2 + // (тот же список, что DDCBaseIndex бэкенда; Saturn раньше был пропущен). + if FNetwork.Device.BoardType in [3, 4, 5, 10] then FActiveDDC := 2 else FActiveDDC := 0; + ResetPanDDCMaps; // пул пан-DDC заново от нового FActiveDDC // Синхронизируем samplerate WDSP если нужно if FWDSPReady and (FDSPEngine.SampleRate <> FSampleRate) then @@ -3473,9 +3599,15 @@ procedure TRadioController.StopRunning; // Graceful-останов RX-потока (Run=0) + сброс TX и RX-телеметрии. Сеть остаётся // Connected — Disconnect отдельно (UI-обёртка делает это после persistence). // Рендер run-кнопки/sleep — UI через OnControllerState(rfRunning); MOX — rfTransmitting. +var + p: Integer; begin if FRunning then begin + // Паны на доп. DDC живут только внутри Run-сессии — гасим до Run=0 + // (3.3 UI пересоздаст по персисту при следующем старте). + for p := 1 to MAX_PANS - 1 do + RemovePanDDC(p); FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True); FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); FRunning := False; diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 76da016..f6bd5b7 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -477,6 +477,11 @@ type procedure PushDDCPacket(const Buf: array of Byte; DataOffset: Integer; IQPairs: Integer); + // IQ доп. панадаптера (аппаратный DDC пана PanId, этап 3). Пока (3.1) + // заглушка-дроп; 3.2 добавит per-pan аккумулятор + Spectrum0(PAN_DISP_BASE+ + // PanId) + ProcessSlices по слайсам этого пана. + procedure PushPanIQ(PanId: Integer; const Buf: array of Byte; + DataOffset: Integer; IQPairs: Integer); procedure FlushRX; // Замьютить RX-аудио на DurationMs от текущего момента. UI зовёт это // на TX→RX в non-DUP только под Windows — там OS-уровневый PortAudio @@ -1523,6 +1528,13 @@ begin RTLEventSetEvent(FQueueSem); // будим DSP поток (как sem_post в piHPSDR) end; +procedure TWDSPEngine.PushPanIQ(PanId: Integer; const Buf: array of Byte; + DataOffset: Integer; IQPairs: Integer); +// Сетевой поток. Этап 3.2 положит пакет в аккумулятор пана PanId; до тех пор +// поток доп. пан-DDC отбрасывается (панов ещё никто не создаёт). +begin +end; + procedure TWDSPEngine.FlushRX; begin FQueueHead := FQueueTail; diff --git a/doc/SLICES_PLAN.md b/doc/SLICES_PLAN.md index 13f33b3..4053e25 100644 --- a/doc/SLICES_PLAN.md +++ b/doc/SLICES_PLAN.md @@ -128,8 +128,10 @@ Pluto+ (AD9363 2r2t, libiio): ### 3.0 — TPanafallPanel: выделить панадаптер из MainForm -**Статус 2026-07-10: фазы A и B1 СДЕЛАНЫ (коммиты c125e59, cc9919c; фаза A -проверена пользователем на экране).** Создан `PanafallPanel.pas`: +**Статус: фазы A и B1 СДЕЛАНЫ (коммиты c125e59, cc9919c) и ПРОВЕРЕНЫ +пользователем на экране (A — 2026-07-10, B1 — 2026-07-11: создание/закрытие +флагов, видимость флага A, мышь/колесо/drag, тик S-метров — работает).** +Создан `PanafallPanel.pas`: - Фаза A: панель владеет view (CPU/GL), контролами (спектр/линейка/сплиттер/ водопад/пан-зум/кнопки зума), полной геометрией стека (`Layout`), механикой сплиттера и мышью пан/зум-полосы. MainForm держит алиасы (`FSpecView`, @@ -218,6 +220,31 @@ static/глобального. (CPU-LUT палитры общий — это о Ретюн пана N>0 = запись `FDDCFreq[ddc]` + `SendFullHP` (без реконфига DDC); смена rate/ADC = `ConfigureDDCs` (как сейчас при смене sample rate). +**СТАТУС 3.1 (2026-07-11): РЕАЛИЗОВАНО, собирается (ewsdr qt6 + ewsdrd), +на эфире не проверено (проверяемо только с 3.2/3.3 — панов ещё никто не +создаёт).** Отличие от текста плана: per-DDC состояние (freq/rate/ADC/enable) +живёт в `THPSDRNetwork` (`FPanDDC*`, пишет `SetPanDDC`/`SetPanDDCFreq` из +`TRadioBackend`), а контроллер держит только per-pan маппинг — эквивалентно +`FDDCFreq[]` из плана, но HP-кадр собирается из одного места. Сделано: + +- `TBackendCaps.MaxPans`/`IndependentPanFreq` (HPSDR 4/True, Pluto 1/False); + `SetPanDDC`/`SetPanDDCFreq` — виртуальные no-op в базе, override в HPSDR. +- `HPSDRNetwork`: `ConfigureDDCs` → кэш главного DDC + `RebuildDDCSpecific` + (главный + активные пан-DDC, каждый со своим rate/ADC); `SendFullHP` пишет + частотные слова всех активных пан-DDC (`Buf[9+ddc*4]`); хелпер + `DDCBaseIndex` (дедуп трёх списков плат); сброс пан-состояния в `Connect`. +- `RadioController`: `MAX_PANS=4`, `PAN_DDC_RATES=(192,384,768)`; + `AddPanDDC/RemovePanDDC/SetPanDDCFreq/PanDDC{Active,Freq,RateKHz}` + (аллокатор hw-индексов от `FActiveDDC+1`, обратный маппинг `FDDCPanMap`); + `OnDDCIQ` роутит: активный DDC → главный тракт, пан-DDC → + `WDSPEngine.PushPanIQ` (пока заглушка-дроп, тело — 3.2); паны гасятся в + `StopRunning`, пул сбрасывается в `StartRunning`. +- ⚠ Попутный фикс: `StartRunning` не включал Saturn (BoardType 10) в список + плат с DDCBase=2 → на Saturn гейт `FActiveDDC` ждал DDC0. Исправлено + (`[3,4,5,10]`, как в `DDCBaseIndex`). +- Частоты панов — сырые Гц железа: XVTR-трансляция на паны НЕ распространяется + (XVTR — свойство главного тракта; решить при 3.3/3.4, если понадобится). + ### 3.2 — WDSPEngine: анализатор и IQ-тракт на панадаптер Сейчас: анализаторы `RX_DISP_ID=0` / `TX_DISP_ID=1` (`WDSPEngine.pas:133`),