Files
ewsdr/doc/SLICES_PLAN.md
T
ew8bakandClaude Fable 5 c125e5978e refactor(slices): этап 3.0A — панадаптер выделен в TPanafallPanel
PanafallPanel.pas: view (CPU/GL), контролы стека (спектр/линейка/сплиттер/
водопад/пан-зум/кнопки зума), геометрия Layout, drag сплиттера и мышь
пан/зум-полосы. MainForm работает через алиасы (FSpecView/PbSpectrum/… =
FPan.*), размеры областей — FPan.SpectrumWidth/SpectrumHeight/WaterfallHeight.
Семантика мыши/флаги/тик пока в MainForm (фаза B). GL-аудит per-instance
пройден (глобальных GL-ресурсов нет). План: doc/SLICES_PLAN.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 22:41:32 +03:00

30 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

Статус 2026-07-10: фаза A СДЕЛАНА (собирается, стартует; на экране не проверено). Создан PanafallPanel.pas: панель владеет view (CPU/GL), контролами (спектр/линейка/сплиттер/водопад/пан-зум/кнопки зума), полной геометрией стека (Layout), механикой сплиттера и мышью пан/зум-полосы. MainForm держит алиасы (FSpecView, PbSpectrum, …) на члены FPan — весь остальной код не тронут; размеры областей читаются из FPan.SpectrumWidth/SpectrumHeight/WaterfallHeight. Семантика мыши спектра/водопада, флаги слайсов, оверлеи, тик — ПОКА в MainForm (фаза B). 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.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)