Files
ewsdr/doc/SLICES_PLAN.md
T
ew8bakandClaude Opus 4.8 1527b0f849 feat(slices): multi-slice receivers (FlexRadio-style) — soft fan-out + per-slice flag/audio
Independent RX slices sharing one panadapter: each has its own
freq/mode/filter/AGC/NR-NB-SNB-ANF, S-meter, volume and sound card.
VFO A = main receiver; B+ are soft fan-out WDSP channels within the
captured span (ideal for Pluto/QO-100; HW DDC path is Stage 3).

- WDSPEngine: soft fan-out (TSliceChan, ProcessSlices in DSP thread,
  per-slice NCO/mode/filter/AGC/NR/NB/SNB/ANF + S-meter, OnSliceAudio),
  FSliceLock serialises add/remove/set vs DSP; reopened on rate change.
- RadioController: TCtrlSlice with its own TAudioOutput/device,
  Add/Remove/SetSlice*, RefreshSliceShifts keeps slices on target Hz
  across centre changes. Slice letters start at 'B' (A = main receiver).
- UI: per-slice flags (VfoOverlay) with letter badge/close, filter-band
  markers in BOTH GL and CPU renderers, drag-retune, wheel; flags placed
  beside the filter band with flip, like the main flag. Ctrl+click adds.
- Main flag (slice A) visible whenever the left panel is hidden OR any
  slice exists; synced with panel-driven DSP/mode/AGC changes.
- Perf: S-meter decoupled from flag chrome cache (FMeterDirty +
  meter-only RefreshMeter via EnsureRendered); InvalidateVfoOverlay
  avoids global overlay-cache churn on meter ticks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 23:46:30 +03:00

182 lines
14 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)
Статус: **план; UI-каркас флага слайса уже сделан** (см. «Что уже готово»),
DSP/роутинг (Этапы 1/3) — не начаты. Ничего не коммитить без явной просьбы.
## Что уже готово
`VfoOverlay.pas` переделан в **«флаг слайса»** в стиле SmartSDR (6 коммитов
`4a92256..a04c592`, уже в `main`). Это ровно UI-управление одним слайсом из
Этапа 2, только пока в единственном экземпляре:
- Флаг-накладка поверх спектра с буквой слайса (`FSlice`, сейчас захардкожено `'A'`),
fly-out пикерами **MODE / DSP / AUD**, слайдером громкости, mute, SPLIT,
кликабельным **TX-бейджем** (выбор TX-слайса) и S-метром.
- **Per-flag выбор аудио-устройства** уже есть: `SetAudioDevices` + `OnAudioDevice`
(`VfoOverlay.pas:31,125,136`) — прокручиваемый список в AUD-fly-out. Это готовый
UI под «своё звуковое устройство на слайс» из абстракции `TSlice.DeviceIndex`.
- События наружу: `OnSelect/OnDSPChange/OnAGCChange/OnVolume/OnMute/OnAudioDevice/`
`OnSplit/OnTxSelect/OnInvalidate` (`VfoOverlay.pas:131-139`).
- Автопозиционирование: флаг флипается влево, если не влезает справа (`6ad5f29`).
Привязка в приложении **пока одиночная**: один `FVfoOverlay` в `MainForm`
(`MainForm.pas:125`), созданный/подключённый на `MainForm.pas:962-972`, привязан к
VFO A (`UpdateVfo(FController.FVfoA)`, `MainForm.pas:3265`); хендлеры
`OnVfoOverlay*` шлют изменения в главный тракт. `SpectrumView` держит один
`FVfoOverlay` и рисует его в `DrawOverlay` (`SpectrumView.pas:72,220,1010`).
Чтобы превратить это в мультислайс — нужно из одиночного флага сделать **коллекцию
флагов (по одному на слайс)** и развести их события на конкретный `SliceId` вместо
глобального VFO A. Детали — в обновлённом Этапе 2.
## Цель
Несколько независимых приёмных «слайсов» на одном ADC/одном IQ-потоке:
у каждого слайса своя частота, режим, полоса, AGC и **своё звуковое
устройство** (как DAX/наушники/динамик во Flex). Панадаптер (спектр+водопад)
остаётся один, слайсы рисуются маркерами поверх.
## Как это устроено у Flex и что из этого берём
Слайс во Flex — это **не «ещё один фильтр» на одном приёмнике**, а полноценный
независимый узкополосный приёмник (свой DDC в FPGA + демодулятор + фильтр + AGC +
аудио). Общий у слайсов — только панадаптер (широкий FFT-захват).
Один WDSP-канал = один демодулятор = один фильтр = один аудиопоток. Значит для
«разный звук/режим/полоса на каждый слайс» нужен **отдельный WDSP-канал на слайс**.
Способ получить узкополосный IQ для канала различается по железу — см. ниже.
## Ключевое различие бэкендов
```
openHPSDR (Protocol 2, V4.3, MAX_DDCS=80):
ADC0 ─┬─ DDC (FPGA-тюнер) → IQ-поток порт 1035+n → WDSP-канал → фильтр/AGC → AudioOut[dev]
├─ DDC → ...
└─ DDC → ...
• Аппаратные DDC: слайс тюнится КУДА УГОДНО в полосе ADC (весь КВ), дёшево по CPU.
• На Angelia/Orion/Saturn DDC0/DDC1 заняты PureSignal → слайсы с DDC2+ (в коде DDCBase=2).
• RX2 = ВТОРОЙ ADC (ADC1), отдельная антенна/diversity — ОРТОГОНАЛЬНО слайсам, не нужно.
Pluto (AD9363, libiio):
один RX-тракт: один LO, один rf_bandwidth, один IQ-поток (притворяется одним DDC,
пушится как TDDCIQPacket в тот же конвейер).
• Аппаратных DDC НЕТ. Слайсы = СОФТОВЫЙ fan-out одного IQ-блока на N WDSP-каналов,
каждый со своим SetRXAShiftFreq в пределах захваченной полосы вокруг LO.
• Для QO-100 идеально: весь транспондер (~500 кГц) в захвате, LO залочен на маяк.
```
## Единая абстракция
```pascal
TSlice = record
Id: Integer;
TargetHz: Double; // абсолютная частота слайса
Mode: TRxMode; // USB/LSB/CW/FM/...
FilterLo,
FilterHi: Integer;
AGCMode: TWDSPAGCMode;
Volume: Double;
WDSPChannel: Integer; // канал WDSP (0..N); 0=главный, 1=TX заняты
Source: (srcSoftShift, srcHardDDC); // как получаем узкий IQ
DDCIndex: Integer; // если srcHardDDC — какой аппаратный DDC
ShiftHz: Double; // если srcSoftShift — сдвиг = TargetHz - CapturedCenterHz
AudioOut: TAudioOutput; // свой инстанс со своим DeviceIndex
DeviceIndex: Integer; // выбранная звуковуха (или -1 = default)
Enabled: Boolean;
end;
```
Правило маршрутизации по частоте (одно на оба бэкенда):
- слайс **внутри** текущего захваченного span → `srcSoftShift` (Pluto и HPSDR);
- слайс **вне** захваченного span → `srcHardDDC` (только HPSDR; Pluto так не умеет).
## Порядок работ
### Этап 1 — Софтовый fan-out в WDSPEngine (backend-agnostic)
Даёт слайсы сразу на Pluto/QO-100 (единственный режим) и на HPSDR для слайсов
внутри видимой полосы. Pluto уже пушит IQ через тот же DDC-конвейер, поэтому
fan-out живёт в движке и не зависит от бэкенда.
- `WDSPEngine.pas`: сейчас `RXA_CHAN=0` один канал, один `FRXOut`, один аудиовыход
(`WDSPEngine.pas:96`, alloc буферов ~`:159`, OpenChannel ~`:1081`).
- Добавить массив слайс-каналов: для каждого активного слайса `OpenChannel(chN, ...)`
с той же геометрией, что RXA_CHAN, + `SetRXAShiftRun/SetRXAShiftFreq(chN, ShiftHz)`
(механизм уже используется: `WDSPEngine.pas:1099-1100`), `SetRXAMode`,
`RXASetPassband`, AGC, `SetRXAPanelGain1`.
- В DSP-потоке после накопления IQ-блока (`FRXAccI/Q`, `FRXAccPos`) прогонять
`fexchange0` по КАЖДОМУ активному каналу из ОДНОГО и того же входного буфера
(входной буфер не портить in-place между каналами — держать копию, как `FSpecBuf`).
- Выход каждого канала → свой callback `OnSliceAudio(SliceId, L, R, Count)`.
- `AudioOutput.pas`: уже готов к мульти-устройству — `DeviceIndex`, `EnumOutputDevices`,
`FindDeviceByName` (`AudioOutput.pas:137-151`). Создавать **отдельный `TAudioOutput`
на слайс**, каждый со своим `DeviceIndex`.
- `RadioController.pas`: `TSlice[]` (add/remove/retune/setmode/setdevice), роутинг
`OnSliceAudio` → нужный `TAudioOutput`.
Проверка Этапа 1: Pluto/QO-100 — 2 слайса (VFO + маяк-монитор), звук в две разные
звуковухи, независимые режимы/полосы. Сборка: `lazbuild --ws=qt6 ewsdr.lpr`.
### Этап 2 — UI слайсов на водопаде + выбор устройства
Каркас флага слайса уже сделан (см. «Что уже готово»): один `TVfoOverlay` со всеми
пикерами и per-flag выбором устройства. Остаётся размножить его на N слайсов и
привязать к позиции по частоте. Выпадашка устройства и dropdown-паттерн **уже есть в
самом флаге** (AUD fly-out) — отдельный `DeviceForm` не нужен.
- **Коллекция флагов вместо одного.** Из `MainForm.FVfoOverlay` (одиночный,
`MainForm.pas:125`) сделать `array of TVfoOverlay` / список, по флагу на слайс;
`SpectrumView.FVfoOverlay` (`SpectrumView.pas:72`) — тоже коллекция, рисовать все в
`DrawOverlay` (`SpectrumView.pas:1010`). Разместить: `SetSlice(Id, Letter)` в оверлей,
события `OnVfoOverlay*` должны нести `SliceId` (сейчас бьют по VFO A напрямую —
завести маппинг оверлей→слайс, роутить в нужный `TSlice`/WDSP-канал из Этапа 1).
- **Позиционирование по частоте.** `PositionVfoOverlay` (`MainForm.pas:518`) сейчас
ставит один флаг; расширить на маппинг `TargetHz → X` через `GetViewWindow`/
`SyncSpecViewFreq` (зум-работа). Слайс за краем видимой полосы — маркер у края
(флип влево/вправо уже есть, `6ad5f29`).
- **ЛКМ по водопаду** = поставить/двинуть активный слайс; выделение активного
(подсветка буквы/рамки флага). Хит-тест мыши флага уже разведён
(`HandleMouseDown/Move/Up`, `MainForm.pas:4424`) — добавить «клик мимо флага по
спектру = ретюн активного слайса».
- **TX-select** уже кликабелен (`OnTxSelect`, `08c71c4`) — при мультислайсе выбирать,
какой слайс идёт в TX; снимать флаг с остальных.
- Персист: список слайсов в `Settings.pas` (per-band или глобально — решить), включая
`DeviceIndex`/имя устройства каждого флага.
### Этап 3 — Аппаратные DDC для openHPSDR (слайс куда угодно по КВ)
Апгрейд поверх общего каркаса; нужен только чтобы ставить слайс ВНЕ захваченной
полосы. Плита ~на 70% есть:
- `ConfigureDDCs(NumDDCs, ...)` уже включает N DDC с per-DDC конфигом/ADC-source
(`HPSDRNetwork.pas:220`, `DDCConfig[ddc*6+0]=ADCSource`).
- Приём разведён по DDC: порты `1035+n``HandleDDCIQ(Buf,Len,DDCIdx)`
(`HPSDRNetwork.pas:392`).
- High Priority уже держит частоту на КАЖДЫЙ DDC (bytes 9-12=DDC0, 13-16=DDC1, …)
`HPSDRProtocol.pas:271`.
Чего не хватает:
1. Per-DDC частота: сейчас `SetRunAndFreq(...; DDC0FreqHz; ...)` знает только DDC0
(`HPSDRNetwork.pas:224`) — писать частотное слово каждого DDC в High Priority.
2. Роутинг `HandleDDCIQ(DDCIdx)` → соответствующий WDSP-канал слайса.
3. Учёт DDCBase=2 (PureSignal) и Ethernet-бюджета (реально 4–8 слайсов, не 80).
`feature/rx2-support` — это про ВТОРОЙ ADC, не про слайсы; но её каркас
(доп. WDSP-канал + доп. аудиовыход + роутинг по индексу) частично переиспользуем.
## Оговорки / риски
- CPU софтовых слайсов ~линейно от их числа: каждый WDSP-канал жуёт полный rate и
децимирует сам (аппаратный DDC дешевле — децимирует железо). Для QO-100 span мал,
несколько слайсов — норма.
- Не портить входной IQ-буфер между каналами (fexchange0 пишет in-place).
- Pluto: слайсы только в пределах захваченной полосы; выход за край = ретюн LO
(двигает всё, при QO-100 LO залочен на маяк — не трогать).
- Пороговая аккуратность: старт/стоп WDSP-каналов на лету (см. заметку про задержки
RXA стоп/старт в `WDSPEngine.pas:~230`).
## Связанные заметки памяти
- project_pluto_integration, project_qo100_zoom_panel, project_qo100_decoder_lock
- project_spectrum_zoom (GetViewWindow / оверлеи / маппинг мыши)
- project_channels_feature (dropdown/форма-редактор как паттерн UI)