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

33 KiB
Raw Blame History

Мультислайсы и панадаптеры (как во 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.
  • RadioControllerTCtrlSlice (абс. 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 все текстуры (FGridLabelTexFLetterTex, 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)