Юзер: на слайсе включил FM с шумодавом, переключил модуляцию — звука нет,
а шумодава в не-FM режимах нет, выключить нечем.
Блок fmsq WDSP стоит в тракте и глушит звук по шумовому выходу FM-
дискриминатора. У главного приёмника он всегда гейтится режимом
(ApplyModeFilter: SetFMSquelch((FMode = MODE_FM) and FFMSQOn, …), в не-FM
ветке — явное выключение), а у слайсов SetSliceFMSquelch дёргал
SetRXAFMSQRun(Chan,1) безусловно и после ухода из FM squelch оставался.
WDSPEngine: новый ApplySliceFMSQ(Idx) — FMSQOn and (Mode = MODE_FM), то же
правило, что у главного. Зовётся из SetSliceMode (ветка без пересоздания
канала) и SetSliceFMSquelch, тем же гейтом накрыт OpenSliceChannel
(пересоздание канала при смене rate/WFM). Выбор юзера не теряется: FMSQOn
остаётся в записи слайса и в персисте, при возврате в FM восстанавливается.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вторая калибровочная таблица рядом с S-метром (паритет с Display Cal Offset
в Thetis): у display-тракта своё окно/детектор, и его уровень расходится с
показаниями стрелки на пару дБ, поэтому таблицы раздельные.
- Settings: DispBandDB[12] / DispXvtrDB[8] (JSON cal_disp_band_i /
cal_disp_xvtr_i, дефолт 0 — картинка не меняется до первой правки).
- WDSPEngine: колбэк OnDispCal(PanId) — движок раз в кадр спрашивает офсет в
UpdateSpectrum и прибавляет к пикселям (главный спектр/водопад в копирующем
цикле, паны in-place при ненулевом офсете). На TX-дисплее не применяется.
Так обошлись без хуков на каждое присваивание FCenterFreq.
- RadioController: общий CalLookup(Hz, BandTbl, XvtrTbl) — на нём SMeterCalFor
и DispCalFor(PanId): главный дисплей по FCenterFreq, пан по FPanFreqHz[p],
т.е. кросс-банд паны получают свой офсет. Колбэк вешается в CreateEngines,
поэтому работает и в GUI, и в ewsdrd; web-зеркало получает готовые кадры.
- SettingsForm: группа «Spectrum / waterfall» с той же разбивкой (диапазоны +
включённые трансвертеры активного железа) и кнопка «= S-meter» — копия
таблицы стрелки как старт калибровки; общая раскладка сеток вынесена в
LayoutCalOffsets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вместо одного офсета на всё устройство — таблицы «на диапазон» и «на слот
трансвертера»; состав таблиц задаёт активное железо.
- Settings: SMeterOffsetDB -> SMeterBandDB[12] / SMeterXvtrDB[8] (JSON
cal_smeter_band_i / cal_smeter_xvtr_i). Легаси-ключ cal_smeter_db становится
дефолтом всех слотов — показания после обновления не меняются. Разделение
Pluto/openHPSDR даёт сама секция JSON: она ключуется по MAC устройства
(у Pluto — synthetic MAC из serial).
- Офсет убран из WDSPEngine (FSMeterCalDB): движок не знает ни диапазонов, ни
трансвертеров. Теперь TRadioController.SMeterCalFor(VisibleHz) — окно
включённого XVTR-слота приоритетнее диапазона, индекс диапазона по плану
железа (FreqToBandIdx с IsPluto); ReadSMeterDBm для главного S-метра
(MainForm + ewsdrd) и SliceSMeter по TargetHz слайса — паны N на других
диапазонах считаются по своему банду, а не по главному.
- SettingsForm: таблицы на 12 слотов, подписи/видимость/раскладку ставит
RelayoutCalTab; SetCalibrationBackend(IsPluto) вызывается рядом с
SetAntennaBackend. У openHPSDR — HF-план, у Pluto — VHF/UHF-план и скрытые
группы Power/SWR detector и Supply V/A (нет Alex-моста и его телеметрии).
Слоты трансвертеров показываются только включённые, с их именами.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Разбор «IMD3 хуже, чем в Thetis»: юзерская плата (Orion MkII/Anvelina
PRO3) при стоковой цели feedback 152 перегружает входные каскады
ADC2208+кодека — фидбек сам интермодулирует, PS корректирует по
искажённому наблюдению. Замеры eu2av (Thetis-Enhanced): оптимум FB≈22
при ATT≈10 дБ.
- PSFBTarget: цель уровня feedback, авто по плате (board 5 → 22, иначе
152), настройка ps_fb_target (0 = авто)
- авто-ATT: пороги от цели (FB > 1.5×цели / FB < 0.7×цели при ATT>0),
ddB = 20*log10(FB/цель). Заодно чинит «ATT всегда 0»: старый порог
>181 на этой плате никогда не достигался
- PSOutlierSigmaEff → SetPSOutlierSigma (новый экспорт libwdsp): MAD-
фильтр выбросов перед фитом calcc, авто σ=5.0 для Orion MkII,
настройка ps_outlier_sigma (-1 = авто)
- лампа FB в поповере: зоны от цели (0.7–1.3× зелёная), подпись
«FB: тек/цель»
Требует libwdsp с патчами eu2av (форк wdsp, коммит d27684c).
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>
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>
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>
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>
На каждый сэмпл (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>
В 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>
Всплески на водопаде + щелчки в аудио при 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>
Инструментация (ВРЕМЕННАЯ, не мержить в 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>
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>
Add a per-operator toggle to hear your own downlink off the satellite
while transmitting (full-duplex self-monitor), and restrict QO-100-only
controls to the QO-100 mode.
- WDSPEngine: new MonitorRXDuringTX flag; audio gate becomes
`not FTXActive or (FKeepRXDuringTX and FMonitorRXDuringTX)` so RX audio
can pass to speakers during TX in full-duplex.
- Controller: FRxMuteOnTx (default True = mute RX on TX), SetRxMuteOnTx/
ApplyRxMuteToEngine, rfRxMuteOnTx notify; new InQO100 predicate
(Pluto + full-duplex XVTR slot) as the single gate for QO-100 UI.
- Settings: global RxMuteOnTx (rx_mute_on_tx, default True), persisted.
- MainForm: new RX MUTE button in TX panel next to DUP, visible only in
QO-100; BEACON button visibility tightened from "any transverter" to
InQO100 so it no longer shows in plain VHF/UHF modes.
- doc: plan updated (RF-AGC done, two temps, TX/RX locked split,
self-monitor, phase 6 done; remaining TODOs incl. web wiring).
Web intentionally left unchanged for now.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Builds on the beacon-lock baseline: keeps the proven display-FFT control
path (hardware-LO trim folded into the active transverter LOError), and
hardens it. The narrowband NCO/FLL scaffold (BeaconLock.pas) is removed —
it was never in the control path.
- RadioController:
* Persist live: each correction folds straight into the active xvtr
LOError (visible in Settings, survives restart as LNB calibration);
disk writes throttled. FBeaconLockHz is now only a session indicator.
* Robust tracking vs nearby signals: narrow ±2 kHz gate while locked
(±6 kHz only for acquisition), slew-rate outlier rejection (a sudden
centroid jump = interferer, not slow LNB drift → ignored), and lobe
shape validation in MeasureBeaconFFT (width + power symmetry) so a CW
carrier / one-sided neighbour isn't mistaken for the BPSK beacon.
Prominence measured over the in-window noise floor (gate-width robust).
* Fast convergence: feed-forward (predict post-retune position so the
window follows the beacon through the jump) + near-deadbeat correction
on the manual click → one click locks instead of repeated tapping.
* Single central BPSK beacon (10489.750); dead CoarseBeaconOffset and
unused throttle counters removed; stale NCO/FLL/4 Hz comments fixed.
- WDSPEngine: drop the per-sample TBeaconTracker RX-IQ tap (tracker gone).
- MainForm: BEACON is now a plain button, shown only on Pluto in a
transverter. Lock status (off / click / search / LOCK + correction +
prominence) moves to status-bar field 7 in place of PLL on Pluto;
openHPSDR keeps PLL there.
- SettingsForm/StatusBar: widen LO Error field to ±10 MHz (QO-100 LNBs
drift hundreds of kHz) and widen status field 7 for the beacon text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Click the beacon on the spectrum; the controller tracks it and continuously
trims the transverter LO so the whole downlink stays put as the LNB drifts.
- BeaconLock.pas: TBeaconTracker scaffold (narrowband NCO/FLL) — kept but no
longer in the control path (FLL diverged on the suppressed-carrier BPSK
beacon + Pluto DC offset). Measurement now uses the proven display-FFT path.
- RadioController: FBeaconLockHz folded into XvtrTranslate (on top of LOError).
MeasureBeaconFFT finds the beacon by POWER-WEIGHTED CENTROID of the lobe
(BPSK has a suppressed carrier → broad symmetric ~2.4 kHz lobe, not a line;
a peak-finder jitters, the centroid sits on the carrier). Track position in
ABSOLUTE display Hz (FBeaconTrackHz) so scrolling the waterfall/centre never
moves the search window off the beacon. Closed loop: e = tracked - ref,
d(e)/d(LockHz) = -1 -> LockHz += gain*e; settle cooldown after each correction
(display-FFT lags the LO retune -> avoids overshoot); deadband 75 Hz +
centroid/prominence smoothing + lock hysteresis for a steady lock.
Correction applied only after the user clicks (manual seed), so a crowded
band can't pull a neighbour to the reference.
- WDSPEngine: SetBeaconTracker + RX-IQ tap (tracker idle now, ~1 check/sample).
- SpectrumView + GL: beacon markers — green = reference (10489.750), orange =
tracked centroid.
- MainForm: BEACON button (RX block, XVTR-only). Flow: BEACON -> "CLICK BCN"
-> click beacon -> "L<corr> /<prom>". ServiceBeaconLock driven by MeterTimer.
Reference defaults to the middle BPSK beacon 10489.750. Tested on-air against
Es'hail-2: locks and holds, survives waterfall drag.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wires the new wdsp WFM API into the engine and adds a WFM preset to the
FM filter row (200 kHz BW / 75 kHz deviation). Also fixes WDSP_SAM
constant which was 12 — that slot is now RXA_WFM in the updated enum.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Gives 44ms jitter margin for WiFi connections. Sound card mic path
is unaffected — StallCount never increments when FOnPullMic fills
the ring every tick.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pre-roll bug: when MIC_PREROLL_SAMP is an exact multiple of OPUS_FRAME_SAMP
(1920/960=2), the N<=Want branch filled the buffer but never flushed it,
losing the first 40ms of audio and leaving the ring empty long enough for
silence blocks to be injected into WDSP.
StallCount threshold raised from 2 to 4 (~43ms) to cover OS scheduler
jitter of up to 22ms, preventing false silence injection when an Opus
frame arrives slightly late relative to the DSP tick.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FM modulation:
- Replace 10 generic FM filter buttons with NFM (2.5kHz dev/11kHz BW) and FM
(5.0kHz dev/16kHz BW); deviation applied to both RX and TX via WDSP
- Add FM squelch panel (SQL toggle + slider 0..100, per-band save/restore)
with WDSP SetRXAFMSQRun/SetRXAFMSQThreshold (threshold = level/200.0)
- Add CTCSS panel with on/off toggle and custom flat-button tone picker popup
(38 standard tones 67.0..250.3 Hz, default 67.0); tone selection and enable
state saved per band via SetTXACTCSS*
- FM panels (SQL, CTCSS) shown only in FM mode, layout via RelayoutBelowBands
- PanelFilter height shrinks in FM mode so panels sit tight below filters
- FlatButton: handle CMTextChanged to invalidate on Caption change
ADC settings (Dither/Random):
- Add DitherEnabled/RandomEnabled to TGlobalSettings (default true)
- Pass as bitmask to ConfigureDDCs DDC Specific packet bytes 5/6
- Exposed in Settings → Advanced tab with live apply via ApplyADCSettings
Web server settings:
- Add TWebSettings (enabled, port, bind_addr, user, pass) stored globally
- WebServer: configurable port/bind IP via ParseIPv4; Reconfigure method
- Exposed in Settings → Advanced tab with live apply via ApplyWebSettings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
gen1 (TXA PostGen) sits after all modulators and the bandpass filter,
so a positive frequency always produces a tone above the carrier.
For LSB and CWL the tone must appear below the carrier — negate the
frequency for those modes. Also re-apply the tone when mode is switched
while TUN is active.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Browser: AudioWorklet accumulates 960-sample frames, AudioEncoder encodes
to Opus, sends binary WS frame 'M' + raw Opus packet
- Server: opcode $02 handler decodes Opus via libopus, calls OnWebMic callback
- MainForm: WebOnMic converts PSingle→Double, pushes to PushTXMicSamplesD;
ApplyMOX switches mic source to txmsWeb when web client active, restores on TX-off
- WDSPEngine: added txmsWeb to TTXMicSource enum
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
В non-DUP при MOX/TUN на возврате слышен и виден на водопаде хвост
своего сигнала. Во время TX RX-IQ оседал в RXA pipeline / FFT-истории
RX-анализатора и «выстреливал» при открытии гейта.
- WDSPEngine: KeepRXDuringTX (DUP-флаг). В non-DUP TX PushIQItemToDSP
дропает пакеты на входе DSP-потока — RXA и анализатор не кормятся.
- Windows-only post-TX hold (250 мс): дропаем И входящие RX-IQ, И аудио
в окне после MOX-off, чтобы скрыть «хвост» от FPGA TX-buffer/PA
slew-down радио. На Linux код-путь не активируется (буфер OS мелкий).
- Network: ClearDUCIQQueue + IsTransmitting guard в DUC sender thread,
чтобы недопереданные TX-IQ пакеты не уходили после MOX-off.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Причина щелчков (особенно слышных в TUN с непрерывной несущей):
TX-поток ждал FTXMicSem с таймаутом PeriodMs (auto-reset event), а
PushTXMicSamples* сигналили семафор каждый push. PullSoundCardMic
зовётся из самого TX-потока → сем сигналится → следующий WaitFor
возвращается немедленно. Плюс bottom-check всегда фигачил silence-блок,
если Avail<BufSize (а это всегда так после inner loop).
В итоге на один реальный период радио (≈21мс @ 1024/48k):
Wake1 (timeout): pull → voice block + silence block
Wake2 (immediate, sem был set): pull N=0 → silence block
= 3 блока IQ × 4096 пар = 12288 пар вместо 4096. На 192k DUC это
≈585k sps вместо 192k → FIFO радио переполняется → щелчки.
Фикс:
- PushTXMicSamples* больше не сигналят семафор (TX-поток сам себя
будил через них). Сем оставлен только для будилки на shutdown.
- TX-поток тикает строго по таймеру, ProcessedAny-флаг гарантирует
silence-блок только когда в этом тике ни одного реального не было.
Per-tick теперь: ровно 1 блок IQ — точно соответствует темпу радио.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Два источника искажений при передаче с микрофона компьютера:
1. SoundCard mic путь в TX-потоке (WDSPEngine.pas) при неполном блоке
подменял голосовой хвост в кольце тишиной. PortAudio отдаёт по 128
сэмплов, в кольце часто < FAudioBufSize → ProcessTXBlock уходил в
else-ветку и слал блок нулей, оставляя голос на следующий тик.
Результат: чередование «голос/тишина» = треск ≈50–100 Гц.
Теперь подмена тишиной только при Avail=0; иначе ждём следующий тик.
Добавлен PushTXMicSamplesD — батч-пуш с одной будилкой семафора.
2. DUC IQ packetizer (MainForm.pas/OnTXIQReady) добивал последний
неполный пакет нулями. FTXOutBufSize не кратен 240 (4096 mod 240=16),
из-за чего в каждом TX-блоке возникал провал ≈1.16 мс каждые ≈21 мс
≈ 47 Гц гул. Теперь хвост переносится в следующий вызов через
FDUCPendingI/Q/Count; шлются только полные пакеты по 240 пар.
Дополнительно: ScaleIQ24 клампит результат в [-2^23, 2^23-1] чтобы
пики от EQ/компрессора (V чуть >1.0) не оборачивались через 24-bit
wrap → щелчки на громких сэмплах.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- BtnTUN рядом с MOX. Включает SetTXAPostGenRun(TXA, 1) с тоном
(mode=0, mag=1.0, freq из настроек) и активирует MOX. PTT/HW
игнорируются во время tune.
- Drive в режиме TUN использует FTXSettings.TUNLevel вместо слайдера,
per-band калибровка FPABandCal применяется одинаково.
- Settings → Transmit → Tune (TUN): TUNLevel 0..100% (def 10),
TUNFreq 100..5000 Hz (def 750). Live-обновление во время tune.