Files
ewsdr/doc/SLICES_PLAN.md
T
ew8bakandClaude Fable 5 5dfd7bbb6c feat(slices): этап 3.1 — per-DDC бэкенд HPSDR (пан-DDC) + фикс Saturn в StartRunning
- TBackendCaps: MaxPans/IndependentPanFreq (HPSDR 4/True, Pluto 1/False);
  SetPanDDC/SetPanDDCFreq — виртуальные no-op в TRadioBackend
- HPSDRNetwork: ConfigureDDCs → кэш главного DDC + RebuildDDCSpecific
  (главный + активные пан-DDC, каждый со своим rate/ADC); SendFullHP пишет
  частотные слова всех активных пан-DDC; хелпер DDCBaseIndex; сброс
  пан-состояния в Connect
- RadioController: MAX_PANS=4, PAN_DDC_RATES 192/384/768; AddPanDDC/
  RemovePanDDC/SetPanDDCFreq/PanDDC* (аллокатор hw-индексов от FActiveDDC+1,
  обратный маппинг FDDCPanMap); OnDDCIQ роутит пан-DDC → PushPanIQ;
  паны гасятся в StopRunning, пул сбрасывается в StartRunning
- WDSPEngine: PushPanIQ — заглушка-дроп (аккумулятор/анализатор — этап 3.2)
- фикс: StartRunning не включал Saturn (BoardType 10) в список плат с
  DDCBase=2 — на Saturn гейт FActiveDDC ждал бы IQ с DDC0
- doc/SLICES_PLAN.md: статус 3.1 + B1 проверена на экране

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:52:31 +03:00

422 lines
33 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Мультислайсы и панадаптеры (как во FlexRadio)
Статус: **Этапы 1 + 2 РЕАЛИЗОВАНЫ и ЗАКОММИЧЕНЫ** (ветка `feature/multislice`,
коммит `1527b0f`; проверено на эфире) + UI-полировка флагов (`30a6616`,
`3049f91`, `a3ac459`).
**2026-07-10: Этап 3 ПЕРЕПРОЕКТИРОВАН.** Вместо старой идеи «off-span слайс с
edge-маркером» принята Flex-модель: **аппаратный DDC = отдельный панадаптер**
(свой спектр+водопад), а слайсы ВСЕГДА живут внутри какого-то панадаптера.
Старый черновик (edge-маркеры + slices-рейл) перенесён в «Отвергнутые
альтернативы» в конце файла. Реализация Этапа 3 не начата.
---
## Что реализовано (Этапы 1 + 2)
- **WDSPEngine** — софтовый fan-out: `TSliceChan` + `FSlices[0..MAX_SLICES-1]`
(`MAX_SLICES=6`), свои WDSP-каналы (`SLICE_CHAN_BASE=2`), `ProcessSlices` в
DSP-потоке (свежая копия IQ на канал, свой NCO/mode/filter/AGC/NR/NB/SNB/ANF +
S-метр → `OnSliceAudio`), `FSliceLock` сериализует add/remove/set vs DSP,
переоткрытие после смены sample rate.
- **RadioController** — `TCtrlSlice` (абс. `TargetHz`, буква, свой `TAudioOutput`/
звуковуха), `Add/Remove/SetSlice*`, `RefreshSliceShifts` держит слайсы на
`TargetHz` при смене центра, гейт `SliceFitsCapture` (вне полосы = Этап 3).
Буквы доп. слайсов начинаются с `'B'` (A = главный приёмник).
- **UI** — по флагу `TVfoOverlay` на слайс (буква-бейдж, «×», fly-out MODE/DSP/AUD,
vol/mute/split/TX, S-метр), маркеры полосы фильтра слайса в **обоих** рендерерах
(GL `DrawSliceFilterMarkers` + CPU), drag-ретюн несущей, колесо над флагом.
Ctrl+ЛКМ по спектру = добавить слайс. Флаг главного (A) виден когда скрыта
левая панель ЛИБО есть слайсы B+ (синхронизируется с правками с левой панели).
- **UI-полировка флагов** (`30a6616`, `3049f91`, `a3ac459`):
- `MainForm.LayoutFlags` — единый packing всех флагов (A+B+) в один ряд;
идентификация уехавшего флага буквой по центру полосы (`DrawBandLetter`
CPU + `DrawBandLetterGL` GL с кэшем текстур букв); палитра
`VfoOverlay.SliceColor(L)`; кнопки через `FlatButton.PaintFlatButton`;
полная тематизация из `TAppTheme`.
- **Перф** — S-метр развязан с кэшем «хрома» флага (`FMeterDirty` + meter-only
`RefreshMeter` через `EnsureRendered`); `InvalidateVfoOverlay` не дёргает
глобальный перезалив оверлеев на тик метра.
Остаток Этапов 1-2 (не сделано): персист слайсов в `Settings` (список +
устройство); per-slice AGC-Top (сейчас общий с главным); полноценный
мультислайс-TX (пока TX только главный).
---
## Модель FlexRadio (итог разбора 2026-07-10)
Во Flex **две независимые сущности**, и это ключ ко всему дизайну:
- **Панадаптер (panafall)** — спектр+водопад. Самостоятельный объект: свой
широкополосный захват в железе, свой центр/span/зум, свои настройки водопада.
- **Слайс** — узкополосный приёмник, ВСЕГДА прикреплён к какому-то панадаптеру
и рисуется на нём флагом. Слайс никогда не имеет собственного спектра.
Тянешь слайс за край — панорама скроллится за ним.
Хочешь слушать другой участок — открываешь **второй панадаптер** (радио выделяет
ещё один захват) и создаёшь слайсы в нём. Лимиты: 6400 — 2 пана/2 слайса,
6600 — 4/4, 6700 — 8/8.
**Второй приёмник (второй АЦП)** у Flex называется SCU (Spectral Capture Unit):
полный тракт «антенный вход → АЦП → захват». 6600/6700 — два SCU, 6400 — один.
У панадаптера НЕТ переключателя «RX1/RX2» — есть селектор **антенны**
(ANT1/ANT2/RXA/RXB/XVTR), и радио само сажает панадаптер на SCU под выбранную
антенну. Слайсы наследуют антенну своего пана. Diversity (когерентное сложение
двух SCU) — отдельный режим поверх.
**Окна:** все панафоллы тайлятся ВНУТРИ одного главного окна (сплиты с
перетаскиваемыми разделителями). Вынос панафолла в отдельное OS-окно штатный
SmartSDR НЕ умеет — наш pop-out будет улучшением поверх Flex-модели.
---
## Итоговая модель EWSDR
```
Панадаптер (спектр+водопад; свой захват IQ; свой ADC-источник; свой span/зум/
│ палитра/wf-AGC; свой WDSP-анализатор)
└─ Слайсы (флаги внутри панорамы; софт-fan-out IQ ЭТОГО панадаптера;
свой WDSP-канал/режим/фильтр/AGC/DSP/звуковуха — уже реализовано)
```
Правила:
1. Слайс всегда принадлежит панадаптеру (`PanId`). Понятия «off-span слайс»
больше нет: хочешь слайс вне захвата — создай панадаптер там.
2. Пан 0 (главный) существует всегда; его «слайс A» — текущий главный тракт
(WDSP-канал 0). Панадаптеры N>0 живут целиком на слайс-механике Этапа 1
(каналы `SLICE_CHAN_BASE+`), при создании пана автоматически создаётся
один слайс в его центре (поведение Flex).
3. ADC-источник — свойство **панадаптера**, не слайса (бейдж `A1`/`A2` в шапке).
В отличие от Flex показываем именно АЦП, а не антенну: на HPSDR-платах
маппинг «вход→АЦП» жёсткий, антенная абстракция не нужна.
4. Буквы слайсов — глобальный пул A..G через все паны (как во Flex A-H):
буква = личность слайса, пан = место жительства.
5. Возможности бэкенда описывает запись capabilities (см. таблицу) — UI по ней
прячет/блокирует кнопки, а не хардкодит «if Pluto».
Матрица бэкендов:
| Бэкенд | Панадаптеров | Незав. частоты панов | АЦП | Примечание |
|-------------------------|--------------|----------------------|-----|------------|
| HPSDR 1 ADC (Hermes) | до 4 (DDC) | да | 1 | ADC-селектор скрыт |
| HPSDR 2 ADC (Angelia/Orion/Saturn) | до 4 (DDC, DDCBase=2) | да | 2 | селектор A1/A2 на пан |
| Pluto (обычный) | 1 | — | 1 | как сейчас, ничего не меняется |
| Pluto+ (2r2t) | 2 | **нет — общий LO** | 2 | пан RX2 частотно связан с RX1 |
```
openHPSDR (Protocol 2, MAX_DDCS=80):
ADC0 ─┬─ DDC2 (широкий) → Пан 0 → анализатор + слайсы A,B…
└─ DDC3 (узкий) → Пан 1 → анализатор + слайсы C,D…
ADC1 ──── DDC4 → Пан 2 (другая антенна) → …
• Каждый DDC тюнится куда угодно, свой rate. DDC0/1 заняты PureSignal
на Angelia/Orion/Saturn (DDCBase=2, HPSDRNetwork.pas:1485).
Pluto+ (AD9363 2r2t, libiio):
RX LO ─┬─ RX1 → псевдо-DDC0 → Пан 0
(один!)└─ RX2 → псевдо-DDC1 → Пан 1 (та же частота/rate, другая антенна)
• Оба канала на ОДНОМ LO/rate, фазокогерентны (база под diversity/пеленгацию).
• Обычный Pluto: один RX разведён на плате — Пан 1 недоступен.
```
---
## Этап 3 (переработан): «DDC = панадаптер»
Подэтапы упорядочены так, чтобы каждый собирался и работал сам по себе.
3.0 — чистый рефакторинг без новых фич, дальше функциональность наращивается.
### 3.0 — TPanafallPanel: выделить панадаптер из MainForm
**Статус: фазы A и B1 СДЕЛАНЫ (коммиты c125e59, cc9919c) и ПРОВЕРЕНЫ
пользователем на экране (A — 2026-07-10, B1 — 2026-07-11: создание/закрытие
флагов, видимость флага A, мышь/колесо/drag, тик S-метров — работает).**
Создан `PanafallPanel.pas`:
- Фаза A: панель владеет view (CPU/GL), контролами (спектр/линейка/сплиттер/
водопад/пан-зум/кнопки зума), полной геометрией стека (`Layout`), механикой
сплиттера и мышью пан/зум-полосы. MainForm держит алиасы (`FSpecView`,
`PbSpectrum`, …) на члены `FPan`; размеры областей — из
`FPan.SpectrumWidth/SpectrumHeight/WaterfallHeight`.
- Фаза B1: панель владеет коллекцией флагов слайсов (LayoutFlags, диспетчер
мыши флагов, Create/DestroySliceFlag, PushSliceFlagState, TickFlags,
UpdateMainFlagVisibility). Флаг A создаёт MainForm и отдаёт через
`AttachMainFlag`; семантические события флагов проводятся `OnWireFlag`,
роутинг по `FPan.CurSliceId`.
**Решение по фазе B2 (мышь спектра/водопада, dirty-флаги/тик, SampleRate/
BandPlan-оверлеи): НЕ переносить сейчас.** Семантика мыши (тюн VFO A/B,
XVTR-клампы, маяк, CTUN-drag) станет per-pan только в 3.3, когда появится
API «ретюн пана» — перенос сейчас потребовал бы хуков (ApplyVfoAHook,
BeaconSeedHook…), которые в 3.3 переделались бы заново. Мышь/тик переезжают
в панель В СОСТАВЕ 3.3 вместе с per-pan семантикой. Для 3.1/3.2 текущего
состояния панели достаточно.
**GL-аудит per-instance ПРОЙДЕН**: в `SpectrumViewOpengl`/`WaterfallViewOpengl`
все текстуры (`FGridLabelTex``FLetterTex`, `FSliceTex`, `FTexture`,
`FMarkerTex`) и LUT водопада — поля экземпляра, глобальных/class var GL-ресурсов
нет; палитры — read-only const (шарить безопасно).
Самый большой и самый важный кусок. Сейчас панадаптер размазан по MainForm:
`FSpecView: TSpectrumView` создаётся и прошивается ~20 свойствами напрямую
(`MainForm.pas:1018-1035`), канва `PbSpectrum: TControl` (TOpenGLControl,
`MainForm.pas:2220`), `FPanZoomBar` (`MainForm.pas:312`), мышиные хендлеры
`PbSpectrumMouse*` (`MainForm.pas:439-445`), `LayoutFlags`, тик обновления.
Сделать composite `TPanafallPanel` (новый модуль `PanafallPanel.pas`,
наследник TCustomControl/TPanel), который владеет:
- канвой (`TOpenGLControl` или `TPaintBox` по `USE_OPENGL` — как сейчас);
- своим `TSpectrumView`/`TSpectrumViewOpenGL`;
- своим `TPanZoomBar` и линейкой;
- коллекцией флагов слайсов этого пана (`FVfoOverlay` пана + `FSliceFlags`)
и `LayoutFlags` (packing переезжает сюда);
- мышиной логикой канвы (тюн/drag/колесо/Ctrl+ЛКМ/drag маркера слайса);
- шапкой пана (см. 3.3) — в пане 0 шапка скрыта до появления пана 1.
Наружу: события `OnTune(PanId, Hz)`, `OnAddSlice(PanId, Hz)`, `OnSlice*`
(существующие слайс-события + PanId), `OnPanClose/OnPanPopOut/OnPanADC`;
входные сеттеры состояния (центр/span/mode/wf-настройки) — то, что сейчас
раздаётся FSpecView напрямую.
MainForm после рефакторинга держит `FPans: array of TPanafallPanel`
(пан 0 всегда), общий тик раздаёт кадры по панам, весь остальной код
(левая панель, FreqDisplay, контроллер) работает с «активным слайсом» —
механика `FActiveFlag/CurSliceId` уже есть, добавляется лишь `ActivePan`.
Критерий готовности 3.0: поведение приложения ИДЕНТИЧНО текущему
(один пан, все фичи слайсов работают), diff в MainForm — только вынос.
Отдельная задача внутри 3.0 — **аудит GL-ресурсов** `SpectrumViewOpengl` на
per-instance: каждое окно/канва = свой GL-контекст, текстуры между контекстами
НЕ шарятся. Проверить: текстуры водопада, LUT палитры, кэш текстур букв
(`DrawBandLetterGL`), текстуры флагов (`FSliceTex`), шейдеры/списки — ничего
static/глобального. (CPU-LUT палитры общий — это ок.)
### 3.1 — Бэкенд HPSDR: per-DDC частота/rate/ADC + роутинг
Плита готова ~на 70 %, дельты точечные:
1. **Per-DDC частота в High Priority.** `SendFullHP`
(`HPSDRNetwork.pas:1552`) пишет частотное слово только активного DDC
(`Buf[9 + DDCBase*4]`). Сделать цикл по активным DDC:
`Buf[9+ddc*4] := FreqToPhaseWord(FDDCFreq[ddc])`. Контроллер держит
`FDDCFreq: array[0..MAX_DDCS-1] of Double`, обновляет при ретюне пана.
2. **Per-DDC rate/ADC/enable в `ConfigureDDCs`** (`HPSDRNetwork.pas:1460`):
сейчас один rate/ADCSource на все N. Принимать массив записей
`(Enabled, RateKHz, ADCSource)` — протокол это позволяет
(`DDCConfig[ddc*6+0]=ADCSource`, rate там же per-DDC).
3. **Роутинг IQ по панам.** `RadioController.OnDDCIQ`
(`RadioController.pas:828`) сейчас гейтит `DDCIndex = FActiveDDC`
(`:875`), остальное выбрасывает. Заменить на маппинг
`DDCIndex → PanId`: пакет пана 0 — в существующий путь, пакеты
панов N>0 — в `WDSPEngine.PushPanIQ(PanId, Data)` (см. 3.2).
4. **Аллокатор DDC**: пул hw-индексов от `DDCBase+1` (DDCBase=2 на
PureSignal-платах, `HPSDRNetwork.pas:1485`), захват при создании пана,
освобождение при закрытии, слот-стабильность как у слайсов.
5. **Rate слайс-панов**: выбор из 192/384/768 кГц (дефолт 384). Бюджет
Ethernet: поток DDC ≈ rate × 6 байт (24-бит I+Q) → 384 кГц ≈ 18 Мбит/с;
3 доп. пана + главный на 1.536 МГц укладываются даже в 100 Мбит с запасом,
но кап «до 4 панов» держим жёстко (и по CPU тоже).
Ретюн пана 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`),
создание `XCreateAnalyzer` (`:1140`), настройки+span-clip
`ApplyRXAnalyzerSettings` (`:1163-1211`), снапшот спектра
`Spectrum0(1, RX_DISP_ID, …)` из DSP-потока (`:1597`), выдача пикселей
через `FOnSpectrum` (`:2850`). Слайсы едят копию ГЛАВНОГО аккумулятора
в `ProcessSlices`.
Дельты:
1. **Пан-анализаторы**: `PAN_DISP_BASE = 8` (не 4/5 — они заняты в ветке
`feature/qo100-zoom-panel`, при будущем merge не столкнёмся);
`XCreateAnalyzer(PAN_DISP_BASE+PanId)` при создании пана,
`ApplyPanAnalyzerSettings(PanId)` — параметризованная копия
`ApplyRXAnalyzerSettings` (свой rate, свой span-clip/zoom, свои
FFT/avg/detector — можно стартовать с общих настроек, разделить позже).
2. **Per-pan аккумулятор**: `TPanChan = record` (`FPanAccI/Q`, `FPanAccPos`,
rate, disp id, активные слайсы). `PushPanIQ(PanId, Data)` из сетевого
потока кладёт в аккумулятор пана; по накоплению блока в DSP-потоке:
копия → `Spectrum0(1, PAN_DISP_BASE+PanId, …)` + `ProcessSlices` по
слайсам ЭТОГО пана.
3. **Слайс знает пана**: в `TSliceChan` добавляется `PanId`; NCO shift
слайса = `TargetHz PanCenterHz`; rate канала слайса = rate его пана
(для панов N>0 это 192-768 кГц — WDSP-каналу дешевле, чем полный rate!).
`RefreshSliceShifts` обобщается на «центр пана», гейт `SliceFitsCapture`
на «span пана».
4. **Выдача пикселей**: `FOnPanSpectrum(PanId, Pixels, N)` (или существующий
колбэк + PanId), MainForm-тик раздаёт по `FPans[PanId]`.
5. **Пул WDSP-каналов** общий: 0=RXA (слайс A пана 0), 1=TX, слайсы 2..7
(`SLICE_CHAN_BASE`, `MAX_SLICES=6`) — на все паны суммарно. Этого хватает:
6 доп. слайсов + главный = 7 приёмников.
Блокировки: `FSliceLock` уже сериализует слайсы vs DSP; добавить такую же
дисциплину для add/remove пана (сетевой поток пишет в аккумулятор пана —
создание/удаление пана только под локом, как переоткрытие слайсов при
ChangeSampleRate).
### 3.3 — UI: второй панадаптер в главном окне
- **Стек панов**: область спектра = вертикальный стек `TPanafallPanel` с
`TSplitter` между ними (Flex-стиль). Пан 0 сверху. Высоты в персист.
- **Шапка пана** (тонкая полоска над спектром, темизирована как флаги):
частота центра · диапазон · rate · бейдж ADC `A1/A2` (3.4) ·
кнопки `⧉` (pop-out, 3.5) и `×` (закрыть; у пана 0 нет). Шапка пана 0
появляется только когда панов ≥ 2.
- **Добавить пан**: кнопка `+` в шапке/тулбаре (только если
`caps.MaxPans > 1`): диалог/дефолт — частота (ввод или «текущая ±»),
rate, ADC. Создание пана автоматически создаёт его первый слайс в центре.
- **Активный пан** = пан активного слайса. Клик по канве пана делает его
слайс активным (существующая механика `FActiveFlag`), левая панель /
FreqDisplay / CAT правят активный слайс — как сейчас.
- **Ctrl+ЛКМ** на любом пане = добавить слайс в этот пан (буква из
глобального пула).
- **Тик/FPS**: общий таймер, кадры раздаются каждому пану; FPS-гейт из
перф-работ применяется per-pan. Свёрнутый/скрытый пан кадры не рендерит.
- **TX**: TX-бейдж на флаге работает с любого слайса любого пана — DUC
независим от DDC, TX-частота = частота слайса. (Полный мультислайс-TX —
отдельная старая задача, тут ничего нового не ломаем.)
### 3.4 — ADC-селектор панадаптера
- `TPanafall.ADCSource: 0/1`; в шапке пана селектор/бейдж `A1`/`A2`.
Показывается только при `NumADCs=2` (детект платы уже есть — `NumADCs`
в ConfigureDDCs-пути).
- Переключение = `ConfigureDDCs` с новым ADCSource для DDC этого пана.
- Пан 0 тоже можно перекинуть на ADC1 — это закрывает сценарий старой ветки
`feature/rx2-support` (вторая антенна) бесплатно, как частный случай.
- Diversity (sync-DDC, оба АЦП когерентно в один канал) — НЕ здесь; протокол
умеет, дизайну панов не мешает, отдельная будущая фича.
### 3.5 — Pop-out: панадаптер в отдельном OS-окне
Улучшение поверх Flex (SmartSDR так не умеет). Кнопка `⧉` в шапке.
- `TPanFloatForm` — простая TForm-обёртка, в которую переезжает
`TPanafallPanel`; `×` окна возвращает пан в стек главного окна.
- **Риск №1 — GL + reparent**: перенос живого `TOpenGLControl` между
окнами на Qt6/Wayland может убить контекст. Безопасный путь: НЕ
репарентить канву, а пересоздать вьюху в новом окне (панель отдаёт
state: центр/span/wf-историю можно не сохранять — водопад начнётся
заново; слайсы/флаги живут в контроллере и переподключаются).
Сначала попробовать честный reparent, fallback — пересоздание.
- **Риск №2 — общие GL-ресурсы**: закрывается аудитом из 3.0.
- Персист: флаг «пан N в отдельном окне» + геометрия окна.
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
- **Детект**: число RX-каналов в iio-контексте (`voltage0/1` + `voltage2/3`
у `cf-ad9361-lpc`); прошивка должна быть с `adi,2rx-2tx-mode-enable`
(на Pluto+ обычно так). Если каналов 2 → `caps.MaxPans=1`, как сейчас.
- **PlutoBackend** (`PlutoBackend.pas:222-236`): сейчас RX-поток пушит
`FOnDDCIQ(0, pkt)`. В 2r2t буфер отдаёт 4 канала интерливом —
деинтерлив на два пакета → `FOnDDCIQ(0, …)` и `FOnDDCIQ(1, …)`.
Дальше это обычные псевдо-DDC, роутинг из 3.1/3.2 работает без изменений.
- **Частотная связка (главное отличие)**: оба RX на одном LO и одном rate.
`caps.IndependentPanFreq = False` → пан 1 создаётся ТОЛЬКО с центром/rate
пана 0; ретюн любого пана ретюнит общий LO (двигает оба); в шапке пана 1 —
признак связки (`🔗 LO`), контролы центра либо скрыты, либо честно тюнят
общий LO. Зум/span-clip у каждого пана свой (анализаторы независимы).
- **Пропускная способность**: ×2 потока. Через гигабитный Ethernet Pluto+
(наш libiio network context) проходит свободно; на USB2 пришлось бы резать
rate — не наш кейс.
- **QO-100 beacon-lock**: лок правит общий LO — оба пана едут вместе,
инвариант «всё стоит на месте» сохраняется, конфликта нет.
- **Бонус на потом**: каналы фазокогерентны — готовая база под честный
diversity (сложение с фазовращателем) и пеленгацию. Не в этом этапе.
- TX2 (второй выход, общий TX LO) — вне этого плана.
---
## Персист (собрать в один заход после 3.3)
В `Settings.pas`:
- список панов: центр, rate, ADC, span/зум, высота в стеке, pop-out+геометрия;
- список слайсов: PanId, TargetHz, mode/filter/AGC/DSP, звуковуха
(DeviceIndex + имя для повторного матчинга), громкость/mute;
- восстановление на старте: сначала паны (ConfigureDDCs), потом слайсы.
Это закрывает и старый долг «персист слайсов» из Этапа 2.
---
## Оговорки / риски
- **CPU**: слайс на пане N>0 дешевле слайса на пане 0 (WDSP-канал жуёт
192-768 кГц вместо полного rate). Дорогое место — рендер: каждый пан =
свой спектр+водопад; FPS-гейт и перф-дисциплина из [[project_perf_profiling]]
обязательны per-pan.
- **Не портить входной IQ-буфер** между каналами (fexchange0 in-place) —
правило Этапа 1, действует и для пан-аккумуляторов (копия до Spectrum0,
как `FSpecBuf`, см. `WDSPEngine.pas:235`).
- **Старт/стоп WDSP-каналов на лету** — задержки RXA стоп/старт
(`WDSPEngine.pas:~230`), создание/закрытие пана держать вне аудио-глитчей.
- **Ethernet**: кап 4 пана суммарно; следить за суммарным rate при выборе
rate панов.
- **DDCBase=2** на PureSignal-платах: доп. панам достаются DDC3+ — при
4 панах на Angelia/Orion/Saturn это DDC2..5, ок.
- **Qt6/Wayland + GL**: reparent канвы (3.5) и per-instance ресурсы (3.0) —
два главных технических риска всего этапа; аудит 3.0 делать первым.
- Смена sample rate главного пана переоткрывает его слайсы (уже реализовано) —
панов N>0 это касаться не должно (их rate независим).
## Открытые вопросы (решить по ходу, не блокеры)
- CAT: что репортить как VFO A/B при активном слайсе на пане N>0
(сейчас CAT смотрит на главный тракт; вероятно «активный слайс = VFO A»).
- Перетаскивание слайса МЕЖДУ панами (drag флага на другой пан): вкусно,
но не в первую итерацию — сначала слайс живёт в своём пане (кламп по
span пана, как сейчас по захвату).
- Авто-promotion (слайс уехал за край пана → предлагать новый пан) — нет,
осознанно: только явное создание пана (решение «сначала статика» остаётся).
- Настройки водопада (палитра/AGC/avg) per-pan или глобально — стартуем с
наследования глобальных, разделим по запросу.
## Отвергнутые альтернативы (для истории)
Старый черновик Этапа 3 предлагал «off-span слайсы»: слайс вне видимой
панорамы на своём hard-DDC, представленный edge-маркером у края спектра
(цветная буква + ◄/► + частота) и/или «slices-рейлом» (полоса-менеджер с
чипами слайсов). Отвергнуто 2026-07-10 в пользу Flex-модели «DDC =
панадаптер»: off-span слайс без своего спектра слепой (не видно обстановку
вокруг), сущности расползаются (слайс с DDC ≠ слайс без DDC), а рейл дублирует
флаги. Идея рейла может вернуться позже как компактный обзор всех слайсов —
но не как замена панадаптеру. Палитра «цвет+буква = личность слайса во всех
местах» из черновика — остаётся в силе.
## Связанные заметки памяти
- project_slices_plan (статус), project_pluto_integration, project_perf_profiling
- project_spectrum_zoom (GetViewWindow / span-clip / маппинг мыши)
- project_qo100_zoom_panel (прецедент доп. анализаторов WDSP, disp id 4/5!)
- project_lazopengl_component (QOpenGLWidget/контексты на Qt6 — база для 3.5)