Селекторы диапазона/samplerate панов N переведены с нативного TPopupMenu на
своё меню в стиле приложения (одинаково на всех платформах, в цветовой теме).
- FlatPopupMenu.pas (new): TFlatPopupMenu — owner-draw меню как ДОЧЕРНИЙ контрол
формы (не top-level окно: Wayland центрирует свои top-level окна). Hover,
галка текущего, клавиатура (↑↓/Enter/Esc), закрытие по клику-вне (MouseCapture)
и потере фокуса. Позиционируется под лейблом-якорем в клиентских координатах.
- PanDisplayPopup: поповер «◑» тоже переведён с top-level формы на дочерний
контрол (центрировался на Wayland) — открывается под кнопкой, закрытие «×»
или повторным «◑».
- MainForm: band/rate-селекторы на FlatPopupMenu; свёртка повторяющегося
выбора темы (9 инлайнов) в CurrentAppTheme.
- PanafallPanel: курсор-«палец» кликабельных лейблов шапки переприменяется в
конце Build (флаги *Clickable могли выставляться до создания лейблов —
samplerate-лейбл не получал crHandPoint).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кликабельный бейдж диапазона («20m») в шапке пана N → popup-меню КВ-бандов.
На выбор — по-флексовому: ретюн DDC на VfoA диапазона (из общего band-кэша,
как пан 0), активный приёмник садится на VfoA с mode/filter/AGC + zoom/pan
этого диапазона, прочие слайсы пана убираются (вне новой полосы). Только на
настоящих КВ (не XVTR, не Pluto).
- TPanafallPanel: FHeaderBandLabel + OnBandClick/BandClickable/SetHeaderBand;
каскад left-лейблов шапки вынесен в LayoutHeaderLabels (band/rate/adc без
дыр при скрытии).
- MainForm: OnPanBandClick/PanBandMenuItemClick/ApplyPanBand +
PanBandSelectorVisible (гейт КВ); band-лейбл в UpdatePanHeaders (по
FreqToBandIdx частоты DDC), тема в ApplyPanHeaderTheme.
Ограничение: per-slice AGC-Top в движке нет — подтягиваются
mode/filter/AGC-mode/zoom/pan, но не индивидуальный AGC-Top диапазона.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Сохранение и восстановление панадаптеров, слайсов и раскладки per-device,
раздельно по контексту: КВ ('hf') и каждый трансвертер ('xvtr_N') — паны
одного контекста не имеют смысла в другом.
Settings: секция "pans"/<ctx> под MAC (grid-режим/доли, на пан —
freq/rate/adc/zoom/pan/доли стека+колонки+сплиттер спектр/водопад, на слайс —
freq/mode/фильтр/agc/vol/mute/squelch/аудиоустройство). TSliceCfg/TPanCfg/
TPansConfig + Default/Save/LoadPans(Ctx).
MainForm: CreatePanCore (вынос ядра из OnAddPanClick), SavePanWorkspaceCtx/
RestorePanWorkspaceCtx/TearDownWorkspace, PanContextKey. Хук контроллера
OnBeforeStop (снимок до гашения DDC в StopRunning). Переходы ActivateXvtrBand/
DeactivateXvtr обёрнуты save-старого/teardown/restore-нового (гейт FRunning);
restore XVTR-панов после ActivateXvtr (FCurrentXvtr целевой ⇒ XvtrTranslate
верный). PanafallPanel.DestroyAllSliceFlags. Смена КВ-банда контекст не меняет.
Round-trip сериализации и разделения контекстов проверены автономно; на
экране не проверено.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Частота пана хранится ВИДИМОЙ (как FVfoA/FCenterFreq) и транслируется в
IF через XvtrTranslate только на границе с железом (AddPanDDC,
SetPanDDCFreq, SetPanDDCRate, SetPanADC) — раньше DDC пана получал
видимую частоту сырьём, а drag водопада пана N клампился пределами АЦП
(~61 МГц) вместо диапазона трансвертера.
Новое: controller.VisibleFreqBounds (диапазон активного XVTR | пределы
железа) для UI-клампа драга; RefreshPanDDCs — на входе/выходе XVTR и
правке LO/LOError клампит видимые центры живых панов, перешивает
freq-слова DDC и прижимает выпавшие слайсы к краям полосы захвата.
Подтверждено юзером на трансвертере.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Грид: пан 0 сверху во всю ширину, доп. паны колонками снизу; тумблер
«▦/▤» в ряду зума пана 0, вертикальные сплиттеры-призраки колонок
(FPanColShare), доля ряда FPanGridRowShare; автоткат в стек при <2 доп.
панах. Регион пана обобщён до прямоугольника (StackLeft/StackWidth).
Фиксы по экранному тесту:
- наезд панов: раздача высот и драг сплиттеров клампятся минимумом
содержимого пана (MinLayoutHeight/PanMinHeight, пан 0 += wideband);
- «блуждающие» кнопки после STOP: MakeFlatBtn даёт Owner=контейнер,
TPanafallPanel.Destroy теперь явно освобождает −/⌂/+/⊞/▦;
- жор CPU ~40%/пан: убран безусловный Invalidate зум-бара из тика
(SyncPanZoomUI), инвалидация только при реальной смене значений;
- пропорция спектр/водопад пана 0 не восстанавливалась после закрытия
панов: кламп ratio минимумами больше не пишется в FSplitterRatio.
Подтверждено юзером на экране: грид/стек/STOP/pop-out работают,
CPU со всеми панами 17-20%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Движок: TPanChan.ZoomFactor/PanSlider, span-clip в ApplyPanAnalyzerSettings
(математика главного, ZOOM_LIMIT 100), SetPanZoomPan с no-op guard (SetAnalyzer
сбрасывает FFT-историю). Зум переживает смену rate/ADC пана.
Контроллер: формула окна вынесена в ViewWindowFor (главный GetViewWindow тоже
через неё), GetPanViewWindow (PanId=0 делегирует), PanZoomIn/Out по лестнице
кратностей с якорем на центре окна, PanZoomDefault, геттеры.
UI: TPanZoomEvent получил Sender; у панов N включён ряд зума, бар
диспетчеризуется по объекту, кнопки −/⌂/+ общие через PanOfZoomButton (матч по
идентичности — MakeFlatBtn создаёт кнопки с Owner=контейнер, PanFromSender их
не видит); SyncPanZoomUI/RefreshPanZoom, вся мышь панов зум-корректна через
GetPanViewWindow. Темизация баров панов N.
Смоук panzoomtest ALL PASS (пик тона совпадает с формулой окна ±0.6%),
подтверждено на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1) LayoutPanStack прятал шапку пана 0 по числу застекованных панов, а wideband
ставится со сдвигом Pan0HeaderH по общему числу (вкл. плавающие) — спектр
накрывал линейку частот wideband. Теперь видимость шапки по PanCount.
2) Лейблы шапки наследовали шрифт формы (ParentFont) — в pop-out окне текст
был дефолтно-тёмным на тёмном. Темизация шапки вынесена в ApplyPanHeaderTheme
(зовётся из UpdatePanHeaders: создание пана/pop-out/dock), окну — шрифт формы.
3) Бейдж ADC-источника переименован A1/A2 → RX1/RX2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ChangeSampleRate больше не делает полный Close/Open движка (стоп DSP-потока,
разрушение всех анализаторов, сброс общей IQ-очереди): гейт FMainGated+FMainLock
дропает только main-пакеты, пересоздаются только RXA_CHAN и слайсы пана 0,
анализаторы панов живут. Контроллер не сбрасывает FWDSPReady на время смены
(OnDDCIQ продолжает кормить паны). Общий код RXA — Open/CloseMainRXChannel.
Бонус: SetChannelState dmode=1 на idle-каналах зря ждал флаш ~130мс → dmode=0
в CloseSliceChannel/CloseMainRXChannel (каналы всегда idle по локам).
Итог: смена 331→120мс, пауза аудио пан-слайса 137→40мс. Смоук ratetest ALL PASS,
подтверждено на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Создание пана: rate наследуется от главного с клампом ≤768к
(вне сетки → 384к); за сменой rate главного паны не следуют
- Клик по «NNN kHz» в шапке пана N → меню полной сетки железа
(PanRateAllowed = Caps.RatePresets ∩ кратные 48 кГц; захардкоженный
PAN_DDC_RATES удалён)
- WDSPEngine.SetPanRate: под FSliceLock пересбор аккумулятора/анализатора
пана + переоткрытие слайс-каналов на новом rate (state сохраняется)
- RadioController.SetPanDDCRate: движок → реконфиг DDC (SetPanDDC) →
кламп слайсов к новой полосе; FPanADC[] хранится (база под 3.4)
- Шапка: rate отдельным лейблом (у панов N кликабелен, акцент/рука)
Смоук-тест без железа ALL PASS (тон: спектр+аудио слайса живы через
384→192→768). Проверено юзером на экране.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Несколько панадаптеров в главном окне (кнопка «⊞», до MAX_PANS):
- TPanafallPanel: регион в стеке (StackTop/Height), шапка с «×»,
pan-aware ViewWindow, FirstSliceId; явный crArrow GL-канвам
(WA_NativeWindow-поверхность на Wayland навсегда наследует курсор
момента создания — прилипала «рука» кнопки «⊞»)
- MainForm: FPans[]/LayoutPanStack (доли + межпанные сплиттеры с живым
призраком), создание/закрытие панов (DDC+слайс+вьюха), пиксели панов
из display-потока под FPanCbLock, рендер по dirty в тике
- Мышь панов N: Ctrl+ЛКМ = слайс, drag маркера = ретюн слайса,
drag фона = live-ретюн DDC (гистерезис 4px), клик = тюн слайса пана
(PanClickTune, снап 100 Гц), флаг-диспетчер только на спектре
- Сплиттер спектр/водопад: drag-математика от региона пана
(FSplitTopOff/FSplitAvailH из Layout), OnSplitterMoved у панов N
- Паны живут в Run-сессии, умирают на STOP
Проверено юзером на экране (3 раунда фидбека) + e2e против hpsdrsim.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
SetZoomPan вызывался из RestoreBand безусловно и переармировал RX-анализатор
(SetAnalyzer = сброс FFT-истории) даже когда zoom/pan не менялись — дисплей
замирал на время наполнения окна. Теперь no-op guard: без изменений выходим
без переарма.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- общая IQ-очередь с TIQQueueItem.PanId (64→256 слотов); DSP-поток
диспетчеризует пан-пакеты в PushPanItemToDSP: декод, feed анализатора
PAN_DISP_BASE+PanId, аккумулятор, ProcessSlicesFor(PanId) под FSliceLock
- пан-анализаторы: Create/DestroyPanAnalyzer + ApplyPanAnalyzerSettings
(rate пана, без зума — пан-зум в 3.3+), живут в OpenAnalyzer/CloseAnalyzer,
общие настройки дисплея перекатываются (ReapplyPanDisplaySettings)
- слайсы в панах: TSliceChan.PanId, AddSlice(..., APanId), OpenSliceChannel
от rate/блока своего пана, SliceDSPRateFor (WFM от rate пана); блок пана
= FAudioBufSize*Rate/FAudioRate => аудио-блок слайсов неизменен
- выдача пикселей: OnPanSpectrum/OnPanWaterfall(PanId,...) из display-потока,
GetPixels пана под FSliceLock, колбэки вне лока
- MAX_PANS=4 переехал в WDSPEngine (единый движок/контроллер/UI)
- контроллер: AddPanDDC создаёт пан в движке до включения DDC, RemovePanDDC
убирает слайсы (со звуковухами)+движок+DDC, гейт/NCO слайсов от центра
своего пана, ретюн пана двигает его слайсы
- смоук-тест на libwdsp без железа ALL PASS: пик спектра пана точно на месте,
слайс пана выдал 1 кГц RMS 0.68, главный тракт жив, RemovePan чисто
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- 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>
Панель владеет коллекцией флагов B+ (создание/закрытие/заливка состояния из
контроллера), раскладкой LayoutFlags, диспетчером мыши флагов, тиком S-метров
(TickFlags) и видимостью флага A (UpdateMainFlagVisibility). Главный флаг A
создаёт MainForm (проводка семантических событий у него) и отдаёт через
AttachMainFlag; новые флаги проводятся через OnWireFlag. Роутинг по слайсу —
FPan.CurSliceId; закрытие «×» — RequestSliceClose/OnSliceClosed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TWebAdapter.SyncMode и TCATAdapter.SyncMode клампили индекс режима по
MODE_SAM (7), тогда как новые режимы дописаны в конец: DIGU=8, DIGL=9,
WFM=10. Команда на WFM/DIGI молча выходила из SyncMode, SetMode не
вызывался — приёмник оставался в прежнем режиме. У web-клиента окно
pend("mode") (600мс) истекало и UI откатывался на старый режим, что
выглядело как «переключение не успело по таймауту». Дело не в самплрейте.
Верхняя граница поднята до MODE_WFM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WFM использует новые wfmd/wfmmod из нашего форка WDSP. Демодулятор —
квадратурный дискриминатор на dsp_rate, поэтому при штатных 48 кГц
девиация 75 кГц не помещается в полосу. В WFM цепь RXA поднимается до
WFMDSPRate (наибольший FAudioRate*2^k <= 192 кГц, делящий SampleRate
нацело), rsmpin децимирует вход сам. Выше 192 кГц смысла нет: энергия
демодулированного тона выходит на полку (33.5k при 192 кГц против 31.8k
при 576 кГц), а FFT дорожает.
Две ловушки WDSP, найденные по дороге:
* firmin.c: nfor = nc / size, idxmask = nfor - 1. Все fircore создаются
с nc = max(2048, dsp_size), поэтому смена dsp_size без RXASetNC даёт
nfor = 0 — пустой массив планов и падение в fftw_execute.
* fir.c/bandpass.c планируют с FFTW_PATIENT, а wisdom.c пишет в
wdspWisdom00 только степени двойки. При SampleRate 576 кГц наивный
dsp_size = in_size = 12288 требовал FFT на 24576 точек — FFTW мерил
план 25 секунд. Отсюда потолок 192 кГц и dsp_size = степень двойки.
RXA и TXA гейтятся раздельно: рейты TXA (48 кГц вход, 192 кГц выход) от
SampleRate не зависят, поэтому wfmmod уходит на широкий dsp_rate даже
когда приёмник остался на 48 кГц.
Заодно исправлен WDSP_SAM: было 12, а RXA_SAM = 10. Раньше 12 было вне
диапазона и WDSP молча игнорировал (SAM работал как DSB); после появления
RXA_WFM = 12 это включало бы WFM-демодулятор вместо SAM. txaMode
расходится с rxaMode после DRM (WFM: RXA 12, TXA 14) — отсюда ModeToWDSPTX.
DIGI-U/DIGI-L получили свою таблицу фильтров (3.0k…300) и полосу от нуля,
а не от среза на 100 Гц, как у SSB. В CAT они замаплены на коды Kenwood 6
(FSK) и 9 (FSK-R) — раньше эти коды сваливались в USB/LSB.
Новые индексы дописаны в конец (DIGU=8, DIGL=9, WFM=10): индекс режима
уходит в settings.json, band-cache, CAT и web-API. В VfoOverlay пришлось
развести хит-коды — режимы кодировались как -(I+1) и налезли на HIT_NR/
HIT_NB/HIT_SNB, а полосы кодировались значением BW, и WFM-овские
120000/160000/200000 налезли на HIT_AGC_PICK_BASE/HIT_DEV_BASE.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
backingScaleFactor главного экрана врёт, когда окно лежит на другом
мониторе. Спрашиваем масштаб у NSWindow того NSView, которому принадлежит
паинтбокс: это ровно то число, в котором Cocoa рисует эту канву, и оно
меняется само при перетаскивании окна между мониторами.
Objective-C по-прежнему живёт только в MacScale; наружу торчит
кроссплатформенная GetControlScale, которая на Windows/Linux сворачивается
в Result := 1.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cocoa рисует канву в физических пикселях, TBitmap всегда 1x, поэтому кадр,
собранный в offscreen-битмап логического размера, растягивался вдвое.
PlatformUtils.GetScreenScale возвращает 2 на Retina и 1 на остальных
платформах. Масштаб — обычная переменная, а не условная компиляция: на
Windows/Linux умножение на 1 даёт прежние числа, а на канву кадр кладётся
через Draw, как и раньше (StretchDraw только при масштабе > 1).
В SMeterView, RulerView и PanZoomBar битмап растёт в S раз, пиксельные
константы, размеры шрифтов и толщина пера умножаются на S. Перо важно
отдельно: иначе на Retina все риски стали бы вдвое тоньше.
RulerView кэширует битмап, поэтому смена масштаба сбрасывает кэш.
PanZoomBar.ThumbGeom получил параметр AScale: отрисовка зовёт его с
физической шириной, хит-тест мыши — с логической, код мыши не менялся.
Objective-C изолирован в MacScale.pas: {$MODESWITCH OBJECTIVEC1} несовместим
с {$MODE Delphi}, а DWARF-3 (-gw3, Debug-режим lazbuild) роняет FPC 3.2.4 с
internal error 200609171 на любых Objective-C типах — отсюда {$DEBUGINFO OFF}.
Замер после правки: 37.5% CPU против 38% базовых, отрисовка этих трёх
элементов ниже уровня шума в профиле.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Бандл под LaunchServices не может передать argv, поэтому GL-режим
включался через bash-обёртку в CFBundleExecutable. Теперь тот же
переключатель читается из окружения, что позволяет задать его в
Info.plist через LSEnvironment и обойтись без обёртки.
Разбор аргументов командной строки не изменён.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На каждый сэмпл (576к/с при 576 кГц) цикл делал заметно больше работы, чем
требовалось:
- IR * SCALE и QR * SCALE вычислялись ТРИЖДЫ — в аргументы FeedDisplaySample,
в FBeaconDec.Feed и при записи в FRXAcc;
- FeedDisplaySample был невстроенным вызовом метода ради двух записей в буфер,
то есть 576 тысяч вызовов в секунду;
- FAnalyzerOpen и FBeaconDec перечитывались из полей на каждой итерации, хотя
внутри цикла не меняются;
- индекс считался умножением Pos := i * 6.
Теперь DI/DQ считаются один раз, FeedDisplaySample развёрнут в цикл (и удалён
как мёртвый код — других вызывающих не было), флаг анализатора и декодер маяка
лежат в локальных, а по данным идёт бегущий указатель.
Замерено зонным профайлером в режиме CPU-времени потока: dsp.push_iq avg
12.5 -> 9.3 мкс, доля ядра 3.0% -> 2.3%.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ReadTelemetry делал 5-6 синхронных iio_channel_attr_read_* — каждое это USB
round-trip, вместе ~30 мс. Звался из MeterTimerTick, то есть прямо в главном
потоке, дважды в секунду (POLL_MS=500). Замер зонным профайлером: тик метра
занимал 5633 мкс настенного времени при пиках до 51 мс, тогда как CPU-времени
в нём было всего 140 мкс — чистое ожидание USB, ронявшее кадры.
Опрос переехал в TPlutoTeleThread: он раз в 500 мс читает те же атрибуты и
публикует снимок под FTeleLock. ReadTelemetry теперь только отдаёт снимок и
никогда не блокирует, поэтому RadioController.ServicePlutoTelemetry и
MainForm.MeterTimerTick не менялись — вся фильтрация температуры на месте.
Прежнее блокирующее тело стало PollTelemetryBlocking (только из потока).
Конкуренция за контекст libiio не выросла: UI-поток и раньше читал атрибуты
параллельно с iio_buffer_refill в RX-потоке. Сменился лишь тот, кто ждёт.
StopTelemetry идёт первой строкой Disconnect — до iio_context_destroy, иначе
поток продолжил бы читать атрибуты разрушенного контекста.
Результат: ui.meter_tick 5633 -> 110 мкс. Значение телеметрии теперь может
отставать до секунды (троттл контроллера + период потока) — на 10-герцовом
индикаторе температуры это незаметно.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Полоса фильтра, края и линия несущей теперь берут цвет из
SliceColor(SliceLetter) вместо захардкоженного янтарного, который
совпадал с бейджем слайса B. Каждый слайс (B/C/D…) получает свой оттенок.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В fly-out DSP появляется ряд SQL (тумблер + порог 0..100) только в режиме FM.
Squelch работает per-slice: у слайсов свои WDSP-каналы, поэтому добавлены
поля/сеттеры FMSQ в TSliceChan/TCtrlSlice, ApplyFMSQToChan и восстановление
при переоткрытии канала. Роутинг события по SliceId (0=RXA, иначе слайс).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace OpenGLContextEx with OpenGLContext in MainForm, SpectrumViewOpengl, WaterfallViewOpengl, and WidebandView.
Switch the Lazarus project/program dependencies from LazOpenGLContextEx to LazOpenGLContext in ewsdr.lpi and ewsdr.lpr.
PixelScale was introduced in 092a202; restore the previous viewport/texture path: glViewport(0, 0, W, H), EnsureTexture(W, H), and UploadCurrentRow(W).
Две сохраняемые громкости: FVolume (RX) и FTxMonVolume (self-monitor
даунлинка на передаче, дефолт 40). Контекст монитора = TX + DUP + RX MUTE
off (тот же гейт, что аудио в движке). В этом контексте слайдер/SetVolume
правят FTxMonVolume, иначе FVolume; ActiveVolume — активная под контекст.
Автопереключение громкости RX<->TX-монитор на T/R (SetMOX) и при смене
RX MUTE / DUP во время передачи (ApplyActiveVolume). Обе персистятся
(tx_mon_volume в конфиге). Веб выровнен на ActiveVolume.
Фикс: слайдер громкости не синхронизировался с загруженным значением после
подключения (в rfDevice синкался только drive, volume пропущен) → слайдер
оставался на 70, движок брал сохранённое → «прыжок» при первом касании.
Добавлен TrkVolume.Position := ActiveVolume в rfDevice.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
RX MUTE (self-monitor даунлинка на передаче): кнопка теперь доступна на
ВСЕХ бэкендах, а не только Pluto/QO-100. Активна только в DUP; без DUP
кнопка disabled и звук на TX не проходит (движок гейтит по
FKeepRXDuringTX=FDisplayDuplex — двойная защита: аудио-гейт + RXA не
кормится). Добавлен RadioController.DisplayDuplex. TFlatButton теперь
рисует disabled-состояние приглушённым (текст/рамка к фону).
TX pre-roll 8144->6096 (2 блока подушки вместо 3): обоснование «нужно 3
блока» опиралось на щелчок ~0.5с после старта, который на деле оказался
bfo-глитчом тона (устранён f2d0519), а не underrun'ом подушки. Латентность
старта TX ~-10мс; startup-щелчков под нагрузкой DUP не слышно (подтверждено).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Всплески на водопаде + щелчки в аудио при TX (openHPSDR TUN/DUP):
периодический ~72° фазовый разрыв передаваемого PostGen-тона на границе
TX-блока, в среднем ~5 раз за сессию, привязан к фазе тона.
Причина: TX-канал открывался с bfo=0 (13-й параметр OpenChannel). При
bfo=0 fexchange0 читает выход из кольца r2 асинхронно (заполняет DSP-поток),
а r2_outidx продвигается на out_size каждый вызов; при рассинхроне DSP-потока
блок изредка читается со швом → разрыв фазы тона (err=0, амплитуда постоянна).
В DUP этот сплаттер принимается обратно = широкополосная строка на водопаде
и слышимый щелчок.
Диагностика A/B: pihpsdr на том же WDSP/радио и на той же частоте 750 Гц —
чист; единственное отличие в открытии TX-канала — bfo=1 (transmitter.c:1316).
При bfo=1 fexchange0 ждёт Sem_OutReady и отдаёт полный выходной блок
синхронно/фазонепрерывно (wdsp/iobuffs.c). Продюсер даёт ровно один вход-блок
на вызов → стойла нет.
Исключено прямыми зондами: рендер, NB/NB2, перегруз ADC, потеря пакетов DDC
(seq), переполнение RX-очереди, временно́й разрыв в принятом IQ, burst
catch-up продюсера, TX-пейсинг.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Два источника фабрикации данных в TX IQ-поток рисовали всплески на водопаде
при передаче (openHPSDR, особенно TUN/DUP):
1. HPSDRNetwork (sender): анти-старв zero-fill при пустой очереди впрыскивал
нулевые пакеты = 1.25мс тишины прямо в поток = разрыв данных = всплеск.
Теперь пустая очередь → просто ждём следующий блок (модель dl1ycf pihpsdr
txiq_thread: данные не инжектируются никогда; короткий разрыв покрывает
pre-roll в FPGA-FIFO радио). Убраны DUC_FIFO_LOW/REANCHOR/ZeroPkt.
2. WDSPEngine (продюсер TTXDSPThread): silence-инъекция при стойле mic
впрыскивала лишний блок сверх mic-темпа (для TUN он нёс PostGen-тон →
боковые/разрыв темпа). Продюсер теперь строго mic-гейтед.
Замерено на эфире: всплески при TUN 5-6/сессию → ~1. Остаточный редкий клик
по прямому замеру НЕ из TX-пейсинга (sender ровно на радиоклоке 800 пак/с
через supply-limiting, дропов/underrun нет) — источник отдельный (дисплей/RX),
разбор позже.
Windows-ветка sender'а не тронута. Pluto — свой TX-тракт (TPlutoTXThread +
libiio DMA), задета только общая silence-removal (push-поток сам добивает
нулями). Обе не перепроверены на железе.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Диагностика временным TXLog (снят) вскрыла цепочку проблем TX-тракта:
- Задержка ~200мс своего сигнала в DUP: за время приёма ринг PortAudio-
микрофона наполнялся до упора (9600 сэмплов) и на старте TX бёрстом
оседал стоячим запасом в DUC-очереди. Фикс: TAudioInput.Flush на RX→TX
(заодно убрана передача в эфир аудио, записанного до нажатия PTT).
- Всплески на TUN: (1) старт — радио начинало с пустого DUC-FIFO; (2) по
ходу — пейсинг со строгой каденцией держал запас FIFO у нуля, джиттер
3-6мс давал underrun; (3) клип: голос из бэклога складывался с PostGen-
тоном (стоит после ALC) поверх шкалы, а сам тон при mag=1.0 давал
выброс Гиббса x1.18 на старте. Фиксы: pre-roll PrimeDUCIQ (THROTTLE +
3 блока подушки в очереди — sender метрит непрерывно, FIFO радио прижат
2000..2400), анти-старв доливка нулями до REANCHOR при просадке,
TUN_TONE_MAG=0.83, мьют mic-входа TXA при активном тоне.
- Огрызок TUN-тона в эфир на первом блоке следующего TX: стоп канала
dmode=1 ждал фейд, который некому прокрутить (продюсер уже остановлен) —
канал стопился по таймауту Sleep(1)x100 с недобранным сигналом в
буферах. Это же было и «остаточным фризом ~106мс» GUI на отпускании
PTT: чистый таймаут, фейд никогда не исполнялся. Фикс: dmode=0 +
синхронный слив/продувка канала 4 нулевыми fexchange0 на стопе и
старте (<1мс, штатные down-slew и flush).
- Дисплей: ResetPixelBuffers(TX_DISP_ID) на старте TX — усредняющая
история TX-анализатора показывала спектр тона прошлой сессии.
- StopRunning при активном TUN не чистил DUC-очередь и не сбрасывал
FTuning/тон — закрыто.
Латентность тракта mic→радио теперь ~43мс (WDSP 11 + подушка 21 + FIFO
10.4). Проверено на эфире: q=0..30, fill/drops/underrun=0, clip=0,
клоки радио/ПК без дрейфа (micRx 48000/с).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
TX-DSP поток (TTXDSPThread) больше не пересоздаётся на каждом T/R — он
создаётся один раз и живёт весь сеанс, на приём/передачу переключается
флагом FTXActive (поток при False просто Continue, не трогает канал/ring).
Тердаун перенесён в TWDSPEngine.Close. Раньше Terminate+WaitFor+Free на
каждом отпускании PTT давал фиксированные ~100мс пустого join-оверхеда
pthread_join, блокируя GUI-поток. Итог: 206→106мс, крупные фризы ушли.
Остаток ~106мс = плавный WDSP fade-out TXA (SetChannelState dmode=1) —
оставлен как есть (dmode=0 дал бы trailing key-click в эфир).
Попутно:
- MainForm: убраны прямые PbSpectrum.Invalidate из drag/wheel VFO/центра —
кадр рисует таймер по FSpectrumDirty (FPS-гейт; прямой Invalidate давал
GL-кадр на каждое событие мыши мимо гейта → треск в HDMI-звуке AMD APU).
- AudioOutput: const-секция после {$ENDIF} блока PA-констант.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- SPECTRUM_PIXELS 1024→4096 + настройка display_pixels (лимит точек
анализатора, SetDisplayResLimit); web-зеркало остаётся 1024 —
max-децимация в SnapshotPixels
- палитры водопада Classic/Inferno/Turbo + регулятор gamma (в LUT),
настройки wf_palette/wf_gamma, страница Waterfall → группа Rendering
- GL-водопад переведён на общий LUT/AGC-уровни базового класса
(убраны дубли ColorForDB/UpdateLevels)
- явные glViewport в GL-вьюхах умножаются на PixelScale (HiDPI),
текстура водопада — в физической ширине
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Пакет LazOpenGLContext заменён на LazOpenGLContextEx (форк в
../lazOpengl): Qt6-бэкенд работает поверх настоящего QOpenGLWidget
(своя C++-либа libqlclglwidget) вместо GLX-контекста на winId(),
поэтому --opengl теперь работает в нативной Wayland-сессии, а под
ws=qt6 открывается путь на Windows/macOS.
- uses OpenGLContext -> OpenGLContextEx (класс TOpenGLControl тот же)
- удалена заглушка QtPlatformAllowsOpenGLControls с советом про
XWayland: больше не нужна
- рантайм-зависимость: libqlclglwidget.so рядом с бинарником
(сборка: make -C ../lazOpengl/csrc install-app APPDIR=bin/x86_64-linux)
Проверено на живом эфире (QO-100): спектр/водопад через GL под
Wayland и xcb, прозрачность FBO-альфы починена в обёртке (forceOpaque).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Canvas на битмапе кадра больше не используется вообще (кроме редкого
ADC-overload пути с восстановлением альфы) — убраны все raw<->canvas
переходы, каждый из которых синкал весь битмап на Qt6 (~2мс):
- RawBegin/RawEnd: прямой доступ к пикселям через RawImage.Data +
BytesPerLine (bottom-up DIB win32 учтён отрицательным шагом)
- RawVLine/RawHLine (с пунктиром), RawFillRect, RawTriangleDown,
RawCurve (кривая = связные вертикальные сегменты по колонкам)
- кэш текстовых масок (TRawTextMask, LRU 48): строка рендерится canvas'ом
один раз белым-на-чёрном в свой мини-битмап, блитится raw с цветом;
все тексты кадра теперь Courier New
- конвертированы: маркер (метка без случайной цветной плашки), бикон-
маркеры, буквы слайсов, слайс-линии, AGC (пунктир 4/2 и 1/2, подпись
с фоном SpecFilter как раньше), края/VFO/TX-курсоры с треугольниками
- пофреймовый альфа-OR проход удалён: альфа чинится один раз в кэше
СЕТКИ после ребилда (canvas-текст там), кадр наследует её через memcpy
Дворд-блендеры (два 16-бит лейна на умножение, /256 вместо /255,
расхождение <0.4%) вместо побайтовых с div 255:
- BandPlanOverlay.CompositeStrip (~1мс -> ~0.2мс)
- SampleRateOverlay.CompositeCache (per-px альфа)
- VfoOverlay.BlendBitmapKey (keyed-магента дворд-сравнением)
- SpectrumView.DrawSpectrumGradient (source-лейны 1 раз на строку,
~1.6мс -> ~0.8мс)
Порог перестройки бэндплана: полпикселя вместо 1 Гц (на QO-100 с
decoder-lock центр подтюнивается непрерывно — был canvas-ребилд полоски
каждый кадр). Композиты внутри raw-лока (их BeginUpdate вложенные).
PerfLog: зона ui.composites отделена от ui.spec_alpha (=RawEnd).
Замер на эфире (Pluto, 576k, 52fps): кадр 8.3мс -> 1.67мс (5x),
со слайсом 2.1-2.3мс; UI-поток ~45% -> ~26% ядра, из них 15% — блиты
paint_spectrum/paint_waterfall (потолок Qt6-вывода). Визуально проверено.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Инструментация (ВРЕМЕННАЯ, не мержить в main):
- PerfLog.pas: зонные таймеры, отчёт раз в 5с в stderr (core% = доля ядра),
EWSDR_PERF=0 выкл, EWSDR_PERF_FILE=путь — дублировать в файл
- Замеры // PERF: сеть (DDC IQ/wideband), Pluto (rx_conv/tx_resamp/tx_fill),
DSP (push_iq/fexchange/slices/tx), аудио, UI (тики/draw/paint/gl_swap,
разбивка draw_spectrum по фазам)
- doc/PERF_PROFILING.md: карта зон, методика, выводы
Оптимизация CPU-рендера спектра (по результатам замеров на эфире):
диагноз — дорога не пиксельная работа, а чередование raw(ScanLine)/
canvas(QPainter) доступа к битмапу на Qt6 (~6 переходов за кадр, каждый
синкает весь битмап; полоса фильтра в тысячи пикселей стоила 4x дороже
полного memcpy кадра). DrawSpectrum перестроен в фазы calc->raw->canvas->raw
(2 перехода): FillBandRaw вместо Canvas.FillRect, DrawSliceFilterMarkers
разделён на raw-полосы + canvas-линии, композиты флагов и альфа-OR в
финальной raw-фазе. Кадр 8.3мс -> 6.1мс; spec_bands 1.87мс -> 0.03мс.
Альфа-OR проход на Qt6 подтверждён нужным (без него canvas-пиксели теряют
альфу и пропадают при блите), сам цикл ~0.08мс — оставлен; тумблер
EWSDR_ALPHA_FIX=0 только для экспериментов. Кисть перед AGC-подписями
теперь ставится явно (была остаточной после перестройки фаз).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Record grounded data-path analysis, per-DDC/RX2 deltas, the soft-vs-hard
WDSP channel split, preliminary decisions (4-6 hard DDCs, static first),
and the UI concept (edge markers, slices rail, ADC badge). Marked as a
work-in-progress draft.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Share one button paint routine: extract TFlatButton.Paint into
PaintFlatButton(canvas, rect, colors) and call it from both TFlatButton
and VfoOverlay.DrawButton — flag buttons now render identically to the
left panel (flat fill + border, no gloss), using theme Btn* colors.
Theme the whole flag card: the structural CLR_* palette (card bg,
border, text, dim, accents, freq, meter bg) is now derived from the
active TAppTheme via ApplyThemeColors, so flags follow light/dark like
the rest of the UI. Status accents (TX/SPLIT) stay fixed on purpose and
use a fixed dark text over their bright badges. Theme is pushed to
flags from ApplyDarkTheme and on creation via WireOverlayEvents.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Manual TextWidth/TextHeight centering pushed the glyph up-left on the
substituted monospace font (Linux). Let the widgetset center it with a
taCenter/tlCenter TTextStyle, fixing the A/×/SPLIT/TX badges.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Flags (A + B+) now lay out in one pass: each sits beside its own filter
band as before, but a left neighbour pushes the next flag right so they
never cross (forward pack + backward pull-in on right-edge overflow).
LayoutFlags replaces the per-flag positioning; PositionVfoOverlay/
PositionSliceFlags are thin wrappers.
Identity is shown by a letter drawn at the top of each filter band in
the spectrum (both CPU + GL renderers), so a flag pushed away from its
band is still traceable. Letters and the flag letter-badge share one
per-slice palette (SliceColor); F is violet, not red, to avoid clashing
with the TX indicator.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>