Commit Graph
78 Commits
Author SHA1 Message Date
ew8bakandClaude Opus 5 b02b68aa6a feat(cw): терминал телеграфа — набор с клавиатуры и декодер приёма
Окно (ПКМ по CWL/CWU, F9, кнопка в настройках CW): одна лента, где принятое
декодером и СВОЯ передача идут вперемешку — своё акцентным цветом. Разделять их
нельзя: в QSK они перемежаются посреди фразы. Под лентой строка набора.

Печать уходит в эфир ПОСИМВОЛЬНО, а не по Enter: строка показывает ровно то,
что ещё не передано (очередь генератора), знаки уходят из неё по мере отправки,
Backspace стирает с хвоста очереди — то, что уже звучит, вернуть нельзя.
Ctrl работает манипулятором (левый точка, правый тире; у кейера прошивки
лепестков нет — там это прямой ключ через бит CWX), по умолчанию выключено,
чтобы Ctrl+C не уводил в эфир. Отпускание ключа ловится и на потере фокуса:
иначе уход из окна с зажатым Ctrl оставил бы несущую в эфире навсегда.

CWDecoder.pas: аудио → текст. Отвод берётся до громкости и мьюта — там нет
программного сайдтона, зато уже отработал узкий CW-фильтр, лучшего предетектора
не найти. Гёрцель гребёнкой из пяти бинов вокруг pitch (заодно показывает
расстройку) → огибающая → адаптивный порог с гистерезисом → длительности →
адаптивная точка → обратная таблица Морзе. Длительности живут в шагах анализа,
а не в показаниях часов: разбор идёт пачками с таймера, и привязка ко времени
вызова ломала бы тайминг на любой загрузке.

Вылезло на тестах и учтено: пик обязан клампиться не ниже пола шума (иначе на
старте порог уходит НИЖЕ шума и первым «знаком» читается собственный шум);
порог дребезга берётся от текущей точки, фиксированный либо пропускает щелчки
на медленной передаче, либо ест посылки на быстрой; длина посылки меряется за
вычетом подтверждения дребезга, иначе скорость занижалась на 15%; расстройка
запоминается только на полной амплитуде посылки, иначе индикатор пляшет.
Граница честная: при вдвое неверной подсказке скорости теряется первое слово —
пока не услышана настоящая точка, длина элемента неизвестна.

Лента расшифровки продублирована строкой под спектром (пан 0), эхо передачи
и очередь набора появились у обоих отправителей — и у локального генератора,
и у кейера прошивки.

★TFlatEdit получил публичный CaretPos: он вставляет знак сам в UTF8KeyPress и
гасит клавишу, поэтому OnKeyPress контрола не вызывается вовсе — из-за этого
набранное «исчезало», а в эфир не уходило. Ввод перенесён на уровень формы.

Проверено оффлайн: 12/20/40 WPM, расстройка, шум, слабый сигнал, цифры и знаки;
плюс сквозной прогон против шести станций CW-стенда в hpsdrsim.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:28:48 +03:00
ew8bakandClaude Opus 5 25fbf10212 feat(cw): программный кейер — телеграф на Pluto/LibreSDR
У openHPSDR точки и тире формирует кейер в FPGA, и это остаётся путём по
умолчанию: тайминги в железе, джиттер PC не влияет вовсе. У AD936x такого
кейера нет вовсе, поэтому телеграфа на Pluto не было в принципе — голосовой
TXA в CW не запускается, а несущую давать некому.

CWKeyer.pas: TCWLocalKeyer рисует манипулированную несущую прямо в поток IQ,
мимо WDSP (эталон pihpsdr cw_shape_buffer: в WDSP режим TXA_CWL — ветка SSB,
да и PostGen не умеет огибающей). Длительности живут В СЭМПЛАХ, а не в
миллисекундах: планировщик ОС влияет только на темп выдачи блоков, а его
сглаживает FIFO бэкенда. Умеет текст, иамбик A/B и прямой ключ; TCWKeyPort
читает манипулятор с модемных линий COM/USB-serial (DTR/RTS питают ключ,
CTS/DSR/DCD/RI — лепестки). Оффлайн-прогон: PARIS занимает ровно 43 точки,
все посылки и паузы 1/3/7 dit с точностью до сэмпла.

Сессия = одна передача, а не одна посылка: PTT, реле T/R, антенна, OC и
аттенюатор Pluto поднимаются один раз и держатся до конца hang — внутри
манипуляция чисто цифровая. Предзаполнение FIFO задано железом: TX-поток
Pluto наполняет буфер целиком (16384 пары ≈ 28 мс на 576k) и добивает
нулями, чего не хватило. Сайдтон эту латентность не наследует — кольцо
огибающей листается, если отставание больше 20 мс.

Сдвиг несущей: у zero-IF ноль бейсбенда совпадает с частотой гетеродина, и
там же его утечка — манипуляция на нуле дала бы backwave ровно на рабочей
частоте. Поэтому несущая идёт на сдвиге, а гетеродин уезжает навстречу через
новую единую дверь TXTuneFreqHz (все пуши TX-частоты переведены на неё).
Знак (CW_TX_BB_SIGN) замерен на эфире, а не выведен.

Развилка источника в UI — один список Keyer: firmware / software (PC) / off;
хранится прежней парой флагов, и на железе без кейера выбор «прошивка»
вырождается в программный, поэтому Pluto работает из коробки. Новое поле
caps HasCWKeyer (раньше это неявно жило в HasHWMic).

Попутно: у AD9361 calib_mode стоял manual_tx_quad, то есть драйвер не
переигрывал калибровку TX quad (она нулит утечку гетеродина) на сменах
TX LO — выставляем auto при подключении.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 17:38:54 +03:00
ew8bakandClaude Opus 5 53329e82e8 feat(cw): телеграф — FPGA-кейер, pitch, сайдтон, передача текста
CW не работал ни на приём, ни на передачу: фильтр стоял симметрично вокруг
нуля (тона не было), понятия pitch не существовало, MOX в CWL отправлял в
эфир голос 2.9 кГц (в WDSP TXA_CWL — ветка SSB), байт CW-опций DUC всегда
был нулевым, поэтому кейер, сайдтон и break-in в прошивке молчали.

Pitch сделан ОДНИМ сдвигом в движке. Кромки фильтра везде остаются
относительно VFO (у CW — симметрично нулю), а гетеродин и полосу двигают
три метода WDSPEngine: CWLOOffset / PushShift / PushPassband. Прямых
вызовов SetRXAShiftFreq и RXASetPassband в проекте больше нет. Так таблица
фильтров не перестраивается при смене pitch (в отличие от Thetis), полоска
на спектре и маркер VFO верны без правок в UI, а слайсы получают свой
сдвиг по своему режиму.

Правила эфира:
- голосовой TXA в CW не запускается вообще (гард по режиму), MOX = только
  PTT; несущую даёт кейер, бит CWX или тон TUN;
- байт 5 собирается из настроек и перепосылается на каждой смене режима и
  TX-слайса — вне CW он обязан быть нулевым, иначе прошивка поднимет PTT на
  замыкание ключа посреди SSB;
- TUN в CW: тон = pitch и встречный сдвиг DUC, несущая встаёт ровно на VFO;
- приёмник на передаче в CW не глушится, иначе умолкает программный
  сайдтон и эфир между посылками.

Программная передача текста (CWMorse): поток с абсолютными дедлайнами
дёргает бит CWX в High Priority, элементы рисует прошивка. Касание
манипулятора или снятие MOX обрывают передачу. Память сообщений — окно
CW Messages и F1..F8 в главном окне. Оживлены CAT KS/KY/ZZKM/ZZKS/ZZKY.

Настройки — вкладка Transmit, подвкладка Hardware, перед блоком FM/CTCSS.

Программный сайдтон точен для передачи текста и прямого ключа; при иамбике
таймингом владеет FPGA, поэтому там верен только аппаратный тон.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 12:20:56 +03:00
ew8bakandClaude Opus 5 82df4dfb8c feat(tx): заводской профиль FM — плотная модуляция с компрессией
Шестой заводской профиль: компрессор 8 дБ + своя кривая EQ. Для FM компрессия
работает иначе, чем для SSB: девиация ограничена жёстко, громкость даёт
средний уровень, а не пики. По цепи TXA (wdsp/TXA.c:xtxa) eqp → leveler → bp0
→ compressor → ALC идут ДО xfmmod, то есть профиль на FM-звук влияет целиком.

Кривая намеренно не задирает верх: предыскажение (xemphp) по умолчанию стоит
вторым вариантом — после ALC, прямо перед модулятором, — поэтому компрессор
его не удержит и лишний буст ушёл бы в перемодуляцию на свистящих. Снизу срез
бубнения, лёгкий подъём разборчивости 1.4..2.3 кГц, спад к 3.4 кГц. Крайние
узлы 200 и 3400 взяты с запасом вокруг AF-полосы модулятора 300..3000, чтобы
обрыв EQ не попал внутрь неё (проверено расчётом по формуле eq.c).

Полоса 300/3000 в профиле для самого FM косметическая — края bp0 движок
считает по Карсону; она сыграет, только если привязать профиль к голосовому
режиму. Девиация, AF-срез и позиция предыскажения остаются свойствами
аппарата (вкладка Hardware) и профилем не переключаются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:47:22 +03:00
ew8bakandClaude Opus 5 0ba808e886 fix(tx): Factory возвращает заводской звук, а не снимок живых настроек
«Default» строился через TXProfileCapture(живые настройки), поэтому сброс,
сделанный на профиле ESSB 5k, оставлял в нём High cut 5000 — то есть Factory
не сбрасывал ровно то, ради чего его жмут. Правило «заводское значит
заводское» было применено только к EQ (FlatEQ), теперь ко всему тембру.

База заводских профилей — DefaultTX, поверх копируется только живая обвязка
микрофона: разъём и режим (Mic In/Line In, boost, bias, PTT, Tip/Ring),
Line In gain и Mic gain. Их сброс сорвал бы настройку микшера. Мощность тоже
остаётся живой — скачок drive ушёл бы в усилитель. Всё остальное (полоса,
компрессор, leveler, ALC, phase rotator, EQ, AM carrier, TUN level) заводское.

Текст подтверждения и подсказка обещали, что полоса и динамика останутся как
есть, — переписаны под новое поведение.

Проверено прогоном: с живыми ESSB 5k + компрессор 12 дБ + Line In + mic gain
22.5 заводской Default выходит 200/3100, comp off, EQ ровный, а Line In,
mic gain и drive сохраняются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:34:36 +03:00
ew8bakandClaude Opus 5 36af031767 feat(tx): привязка профиля к паре «флаг + модуляция»
Одного значения на флаг мало: на слайсе с FT8 и на нём же голосом нужен разный
звук, а слайс/главный меняют модуляцию на лету. Привязка стала таблицей
TTXProfModeTable (режим → индекс профиля, -1 = не переключать), своя у каждого
флага: MainByMode у главного (одна на устройство, общая для КВ и трансвертера)
и TXProfByMode у слайса (сама разъезжается по контекстам — записи слайсов
хранятся отдельно для КВ и трансвертера).

Ключ — режим САМОГО ФЛАГА (FlagMode): у главного FMode, у слайса свой. Выбор в
пилюле пишется в ячейку текущего режима, поэтому смена модуляции достаёт
профиль, с которым в этом виде работали в прошлый раз.

Применение — в трёх точках, все ДО пуша режима в WDSP (ApplyTXChainSettings
внутри SetTXMode перешьёт цепь уже новыми значениями): SetTxSlice, SetMode
(если передаём с главного) и SetSliceMode (если источник — этот слайс).

Персист: tx_prof_modes в записи слайса, main_prof_modes в секции tx_profiles.
Чтение защитное — индекс вне текущего списка профилей читается как -1.
Round-trip проверен прогоном: обе таблицы переживают запись/чтение, незаданные
режимы -1.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:58:46 +03:00
ew8bakandClaude Opus 5 013d0406ac feat(tx): TX-профиль на слайс — пилюля в шапке флага
Передатчик один, профиль до сих пор был один на устройство: активный профиль
звучал независимо от того, какой слайс взял передачу. С панадаптером на FT8 и
вторым на FM это значит, что компрессия и EQ голосового профиля уходили в
эфир на FT8 (FMRAW прикрыт сам — ApplyTXModeSettings глушит там всю обработку,
а DIGU/DIGL нет). У Flex это решается дисциплиной и внешним API; делаем в радио.

Привязка «источник передачи → профиль» (-1 = не переключать, звучит активный):
TCtrlSlice.TXProfile для B..G, TTXProfileList.MainBind для главного (слайс A).
Персист: ключ tx_profile в слайсе пана и main_bind в секции tx_profiles.
SetTxSlice зовёт ApplyBoundTXProfile ДО пуша режима и цепи, иначе
ApplyTXSettingsToDSP лёг бы на старый режим. Привязали источник, который
передаёт прямо сейчас, — применяем сразу.

Привязки — индексы, поэтому RemapTXProfileBindings: удаление профиля из
середины снимает привязки на него и сдвигает те, что правее; Factory-сброс
снимает все (список заменён целиком).

UI: пилюля с именем профиля в первой строке флага сразу за шириной фильтра
(ЛКМ — fly-out список, первая строка «As active»). Правая граница считается
до SPLIT у главного и до RXM у слайса; не рисуем при пустом списке, в DMR
(передачи нет) и в FMRAW (профиль там ни на что не влияет). Прокрутка списка
общая с AUD — блок стрелок вынесен в DrawScrollArrows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 17:06:08 +03:00
ew8bakandClaude Opus 5 8563c4e4cc feat(tx): заводские EQ-кривые ESSB, честная АЧХ в редакторе, фикс 3-полосного EQ
Ключ ко всему — семантика EQ в WDSP (wdsp/eq.c, eq_impulse + TXA.c):
F[1..n] это УЗЛЫ ломаной АЧХ, между ними интерполяция по децибелам, а за
крайними узлами при ctfmode=0 (так создаётся eqp у TXA, мы его не меняем)
идёт кумулятивный скат (f/f0)^4 на каждый бин — фактически обрыв. Значит
включённый EQ работает как второй полосовой фильтр, и верхний узел обязан
лежать за верхней кромкой TX-фильтра.

Settings: заводские ESSB несут свою кривую (AddEQ) — ESSB 3.5k узлы
100..3600, ESSB 5k узлы 100..5100, преамп в минус (EQ стоит до компрессора
и ALC). EQ у Default/SSB DX/SSB Wide сбрасывается в заводской ноль (FlatEQ),
а не наследует живой, иначе «сброс к заводским» не сбрасывал бы тембр.
Сетка узлов вынесена в модульную DEF_EQ_FREQS.

WDSPEngine: единая точка пуша EQ (PushTXEQProfile). Починен 3-полосный
режим: раньше в WDSP уходили первые три узла 10-полосной сетки (100/200/400)
и включённый EQ убивал весь голос выше 400 Гц; теперь legacy-раскладка
самого WDSP (150/400/1500/6000, как SetTXAGrphEQ).

EqualizerControl: кривая — точный порт eq_impulse вместо гауссовых горбов
(сверено с C численно, расхождение 0.0 дБ), рисуется без клампа ±15, чтобы
обрыв за крайними узлами был виден. В 3-полосном режиме ручки стоят на
150/1500/6000 и по частоте не таскаются, сетка профиля при этом цела.

RadioController/SettingsForm/MainForm: кнопка Factory — пересборка заводского
набора с применением Default. Без неё обновлённые заводские профили не доедут
до тех, у кого секция tx_profiles уже записана: она читается, а не создаётся.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 15:47:12 +03:00
ew8bakandClaude Opus 5 71a97b63f0 feat(tx): TX-профили + вкладка Transmit на подвкладках
Профиль — именованный снимок «как я звучу», переключаемый одним кликом
(идиома FlexRadio, состав по таблице TXProfile у Thetis).

Граница «профиль ↔ устройство»:
- в профиль: микрофонный вход (jack/boost/bias/PTT/Tip-Ring/Line-in gain),
  MicGain, кромки TX-фильтра, компрессор/leveler/ALC/phase rotator, EQ,
  AM carrier, Drive и Tune level;
- у устройства остаются ATT on TX, FIR/фаза/окно фильтра (это задержка
  тракта, а не тембр), TUNFreq, FM/CTCSS, TX Display, TX Grid и все
  параметры PureSignal — смена профиля не должна перерисовывать спектр
  и ломать калибровку PS.

Кнопки «сохранить» нет: активный профиль ведётся за живым состоянием
(StoreActiveTXProfile из OnTXSettingsChange и SetDrive) — как и все
остальные настройки в EWSDR, применяемые мгновенно.

Settings.pas: TTXProfile/TTXProfileList, TXProfileCapture/TXProfileApply
(единственные две точки, знающие состав профиля), DefaultTXProfiles
(Default/SSB DX/SSB Wide/ESSB 3.5k/ESSB 5k — наследуют mic-вход и
мощность от текущих настроек), Load/SaveTXProfiles (секция tx_profiles
под MAC). Секции нет — заводской набор строится из текущих настроек,
поэтому миграция старого конфига не меняет звук.

RadioController: FTXProfiles, rfTXProfile, SelectTXProfile (снимок → DSP
+ перепосыл DUC Specific при смене mic-байта + SetDrive + персист),
Add/Rename/Delete (последний профиль не удаляется).

MainForm: кнопка профиля в свободных колонках ряда DUP/RX MUTE — блок TX
не вырос по высоте; ЛКМ — список, ПКМ — Settings→Transmit→Profile.

SettingsForm: страница Transmit разбита на подвкладки Profile / EQ /
Hardware / Display + полоса профиля сверху; свиток 2000 px вместо этого
раскладывается на четыре коротких экрана.

Проверено: round-trip персиста отдельным тестом (все поля, активный
индекс, усечение списка) и offscreen-снимки всех четырёх подвкладок.
На железе не проверено.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 14:21:09 +03:00
ew8bakandClaude Opus 5 ce43116490 fix(filter): слот VAR переживает перезапуск в режиме трансвертера
Симптом: в XVTR-режиме выбрать VAR1, выключить и включить трансивер — фильтр
откатывался на заводской (в FM это NFM). Вне трансвертера всё сохранялось.

Причина: персист XVTR клампил last_filt диапазоном 0..9, оставшимся от эпохи
десяти пресетов. VAR1/VAR2 (10/11) при загрузке схлопывались в 9, а дальше
ClampFilterIdx видел индекс вне таблицы режима (у FM пресетов всего два) и по
своему правилу откатывался на заводской индекс — отсюда NFM. У band-cache
такой границы нет, поэтому в обычном режиме баг не проявлялся.

Граница поднята до FILT_SLOTS-1. Проверено round-trip-тестом SaveXvtr/LoadXvtr:
слот 10 сохраняется и читается как 10.

Проверены заодно остальные пути, где слот мог потеряться: band-cache (клэмпа
нет), каналы памяти (фильтр не хранят вовсе), рендер rfMode (сброс обёрнут
save/restore), web SetFilterBW, SanitizeFilterForMode. Клэмп 0..9 в CAT-командах
FW/SH/ZZBI намеренно оставлен — это ограничение протокола, а не хранения; VAR
снаружи доступен через ZZFL/ZZFH.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 13:22:56 +03:00
ew8bakandClaude Opus 5 25521a2d10 feat(filter): произвольные кромки фильтра — таблица по режиму, редактор, drag, TX-полоска
Фильтр главного приёмника перестал быть одним числом. Теперь это таблица
(Name, Low, High) на режим: 10 пресетов + VAR1/VAR2, знаковые кромки (у LSB
своя строка, у USB своя), паритет с Thetis FilterForm. Слайсы независимые
кромки умели и раньше — главный приёмник настраивался только пресетами.

Модель (Settings/RadioController):
- TFilterSlot/TFilterSet/TFilterTable + заводские дефолты из прежних FILT_*_BW
  и FILT_*_NAMES (переехали в Settings — из них строится таблица).
- FFilterLo/FFilterHi = истина, FFilterBW производная (FilterBWFromEdges),
  поэтому все прежние читатели ширины (web, зум, band-cache, CAT) не тронуты.
- ApplyFilterSlot — единственная точка «слот → состояние тракта».
- SetFilterEdges: произвольные кромки уходят в VAR1 с автопереключением,
  пресеты нельзя испортить случайным движением мыши или энкодера.
- Персист: секция "filters" под MAC, пишутся только режимы, отличные от
  заводских; секция режима сносится целиком, иначе возврат слота к
  заводскому оставлял бы старый ключ.
- Диапазон помнит только выбранный слот: кромки живут в таблице, второго
  источника истины у VAR нет.

UI:
- 12 кнопок фильтра (2 ряда по 6), подписи из живой таблицы.
- FilterPopup — поповер редактора по ПКМ на кнопке фильтра, по образцу
  PureSignalPopup (дочерний контрол формы, не top-level: Wayland). Слева
  слоты, справа Name/Low/High/Width, внизу форма полосы на знаковой оси.
- Перетаскивание края полосы на спектре: ±5px, снап 10 Гц, Alt = симметрично
  (не Ctrl — он создаёт слайс). Отказ на передаче и когда полоса на экране
  уже 12px, иначе захват съедал бы клик-тюн.

Отрисовка:
- Полоса главного VFO рисуется по РЕАЛЬНЫМ кромкам приёмника, слайса — по его
  кромкам; вывод из режима и ширины остался фолбэком. Пара→ширина→пара было
  преобразованием с потерями: несимметричный фильтр из слайс-CAT (ZZFL/ZZFH)
  рисовался не там, где звучал.
- На передаче полоса считается по TX-фильтру (TXSignedEdges), а не по
  приёмной ширине — TX-полоса от RX не зависит (модель Thetis).
- CalcFilterBandXFor больше не держит свою копию таблицы знака боковой:
  единственная таблица теперь FilterEdgesFromBW.

Управление:
- CAT ZZFL/ZZFH заработали на основном порту (были заглушками).
- Андромеда: энкодеры #4/#5 — независимые Filter High/Low, как на железе G2
  (было: #4 ширина, #5 не реализован).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 13:15:33 +03:00
ew8bakandClaude Opus 5 a820182716 fix(web): детализация спектра/водопада — честный размер кадра, поток 'W', настройка разрешения
Веб-пульт рисовал заметно грубее десктопа, и часть причин копилась давно.

Клиент вообще не разбирал бинарный фрейм 0x57: водопад рисовался из кадра
СПЕКТРА, а собственный поток водопада (свой детектор и усреднение
wf_avg_time_ms) уходил в никуда, занимая половину трафика. Теперь водопад
идёт из своего потока.

Адаптер слал константу 1024 вместо реального числа точек. Разрешение
анализатора = ширина панадаптера, и при узкой панели (грид из двух колонок,
маленькое окно) кадр короче — хвост буфера, нули = 0 dBm, уезжал клиенту
белой полосой, а частотная шкала сжималась. В демоне это обходили
DAEMON_SPEC_WIDTH. Длина фрейма теперь равна числу заполненных точек;
клиент и так считал N из byteLength.

Децимация под потолок протокола делила «i*Count div N» — на нецелом
отношении соседние группы получались по 1 и 2 точки, а смещение
max-детектора зависит от размера группы: по спектру шла гребёнка ±3 дБ,
в водопаде — вертикальная рябь. Заменено на целочисленный фактор
(группы одинаковой ширины).

Водопад терял 2 кадра из 3: анализатор даёт до 60 кадров/с, web шлёт 20
строк/с, а бралась просто «последняя». Введено двухступенчатое накопление
max-hold (DSP-поток → PushState → PushLoop) по идиоме
TWaterfallView.SetWaterfallData — короткие посылки CW/FT8 больше не
проваливаются между строками.

Отдельно исправлено в headless: PushState демона (20 Гц) и PushLoop (20 Гц)
идут вразнобой, и на дрейфе фаз одна и та же строка уезжала клиенту дважды
(водопад двоил и полз рывками), а на остановленном RX прокручивалась
застывшая строка. Фрейм 'W' теперь отправляется только при реально пришедшем
от DSP кадре (FWfAccumFresh → WfNew → FWfFresh). Спектр так не гейтится —
это живая кривая.

Рендер в браузере: значение бина для пикселя берётся максимумом по диапазону
вместо «ближайшего» (узкие сигналы мерцали и пропадали на узком экране), при
бинах реже пикселей — интерполяция; сглаживание спектра вынесено отдельным
проходом по всем бинам (в цикле по пикселям часть бинов не обновлялась
вовсе); строка водопада рисуется из сырых бинов, IIR ~200 мс остался только
для слежения за уровнями AGC — он размазывал водопад по вертикали.

Потолок точек кадра вынесен в настройку web.spec_pixels (Settings →
Advanced → Web Server → Spectrum: 1024/2048/4096, дефолт 1024). Буферы
кадра и WS-фреймы выросли под 4096 (WEB_SPEC_MAX, синхронно с
WDSPEngine.SPECTRUM_PIXELS). В демоне эта же настройка задаёт ширину
анализатора — рендера там нет, поэтому DAEMON_SPEC_WIDTH убран.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:35:55 +03:00
ew8bakandClaude Sonnet 5 041bc6ba7a feat(oc): OC Control — Open Collector выходы Penny/Alex (эталон Thetis)
Byte 1401 High Priority packet: per-band (HF 160m-6m) и per-slot (XVTR)
RX/TX-маски на 7 пинов + TX pin action (MOX/TUNE/2TON и комбинации).
Только openHPSDR — у Pluto/AD936x такого выхода нет, вкладка Settings
скрывается на этом бэкенде. Настройки — вкладка "OC Control" в SettingsForm.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 13:47:04 +03:00
ew8bakandClaude Opus 5 20186ce132 feat(slices): аудио-устройства слайса на вкладке Slices (in/out на слот)
У каждого слайса B..G — свои Audio out / Audio in рядом с его CAT-портом.
Списки те же, что у главных комбо; пустой выбор (Default/None) = прежнее
поведение, общий выход/вход приложения.

Это устройство СЛОТА (буквы), а не шаблон:
  • смена настройки применяется к слайсу, стоящему на слоте, немедленно
    (SetSliceSlotAudio) — как и вся остальная форма настроек EWSDR;
  • слайс, который встанет на слот позже (создание/restore из персиста),
    получает то же устройство: слот сильнее аргумента AddSlice;
  • выбор во вкладке AUD флага пишется обратно в настройку слота
    (StoreSliceSlotAudio) — вкладка всегда показывает реальность, а не
    вторую, конкурирующую истину.

Хранятся ИМЕНА PortAudio-устройств, а не индексы: порядок перечисления
между запусками плавает. Имя устройства, которого сейчас нет в системе,
не теряется — комбо добавляет его в список и держит выбранным.

ApplySliceSlotAudio зовётся на старте и на смене устройства (rfDevice):
слайсы восстанавливаются позже, слот должен знать свои звуковухи заранее.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 14:33:42 +03:00
ew8bakandClaude Opus 5 2d15da0462 feat(cat): CAT-порт на каждый слайс + Auto TX (эталон SmartSDR CAT)
Внешняя программа управляет отдельным слайсом как отдельным трансивером
через свой TCP-порт. COM-порты и 19090 остаются за главным приёмником
(слайс A) и не тронуты.

Протокол не дублируется: TCATEngine — чистый парсер поверх записи
callback'ов TCATContext, TCATTcpServer — транспорт поверх движка, поэтому
слайс-CAT = тот же движок с другим контекстом. Новый TCATSliceEndpoint
(движок + TCP-сервер на слот), массивом владеет TCATAdapter. Привязка к
СЛОТУ (буква B..G), не к Id: порт живёт, даже когда слайса нет (PS0).

Auto TX = «Auto Switch TX Slice» из SmartSDR CAT. Вся логика в одной точке
— TRadioController.RequestSliceTx, исполняется в потоке контроллера:
  • пока кто-то уже в эфире (любой источник) — заявка игнорируется,
    перехвата передачи нет никогда;
  • без Auto TX слайс передаёт, только если уже выбран TX-источником;
  • RX; снимает только СВОЮ передачу;
  • TX-источник после отпускания остаётся на слайсе (как в SmartSDR).
На флаге слайса бейдж TX → AutoTX.

Частота слайса — TuneSliceInBand: свобода в пределах ВКЛЮЧЁННОГО диапазона
(трансвертер → его FreqBegin/FreqEnd, иначе band-план), другой диапазон —
отказ. Вне захваченной полосы окно DDC переезжает: доп. пан — центром на
цель (rfPanFreq → UI перекладывает флаги/шапку/зум-бар), пан 0 — центр
посередине между целью и главным VFO и только если оба влезают (общее
железное окно, главный приёмник не оглушаем).

Настройки: вкладка Slices (enable/порт 19091../Auto TX на слайс), персист
в секции cat + device-blob.

Попутно:
  • ZZFL/ZZFH больше не заглушки — новые callback'и кромок фильтра
    (у главного nil → прежний дефолт режима), хелпер SignedPad;
  • FilterIdxFor, AGCModeToUI, BoardUtils.BandEdges;
  • DSP-состояние слайса (NR/NB/SNB/ANF) зеркалится в TCtrlSlice — флаг
    слайса рисовал нули и AGC главного;
  • FUIReady в TMainForm.OnControllerState: адаптеры, создаваемые по ходу
    FormCreate, больше не могут уронить старт рендером до постройки
    виджетов (на этом падал Auto TX из настроек).

Проверено вживую: все порты 19091..19096 слушают, PS;/ZZFA;/MD;/ID;/SM0;/
ZZTX; отвечают; старт со всеми включёнными слайсами чист под gdb.
Документ — doc/CAT_SLICES.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-25 13:56:36 +03:00
ew8bakandClaude Opus 4.8 51e8fc4782 fix(tx): боковая по режиму на всех путях + единая оркестрация TX/RX (эталон Thetis)
Боковая TX теперь строится ЕДИНЫМ конвертером знака TXSignedEdges (порт
Thetis UpdateTXLowHighFilterForMode): магнитуда TX-полосы → знаковые края bp0
по боковой режима. Через него идут ApplyTXChainSettings и SetTXFilterFull —
LSB/DIGL больше не уходят в USB, когда пуш оказывается последним (MOX).

Убраны оба SetTXABandpassRun(1): этот сеттер дёргает bp1 (aux), а не bp0.
При выкл. компрессоре bp1 держал стартовую полосу (−5000,−100) и глушил вход
модулятора — FM/WFM/FMRAW не заводились без «тычка» компрессором. bp1 отдан
компрессору через TXASetupBPFilters, как в Thetis.

Оркестрация сведена: ApplyTXMode (SetTXAMode+ApplyTXModeSettings) — единая
точка TX-перехода для Open/SetMode/SetTXMode (в т.ч. слайс-TX);
ApplyRXModeSettings (WFM/DMR/FMRAW) — для главного RXA и слайсов. Убраны
ручной DMR-инлайн в SetMode и двойной вызов WFM; ApplyWFMSettings→ApplyTXWFMSettings.

Дефолт TX-полосы 100/2800 → 200/3100 (Thetis-паритет).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 19:42:06 +03:00
ew8bakandClaude Opus 4.8 4aa180e7b2 wip(audio): выбор устройства ВВОДА на каждом слайсе + вкладки OUT/IN в AUD
Паритет с уже существующим выбором вывода: на каждом слайсе своё устройство
входа (микрофон/виртуальный кабель).

- RadioController: TCtrlSlice += InDeviceIndex/InDevName/AudioIn (ссылка, не
  владение); mic-пул FMicPool с refcount (Acquire/ReleaseMicInput) — один
  открытый TAudioInput на уникальное PA-устройство, смена TX-слайса не
  переоткрывает поток; ActiveMicInput/ActiveMicInDevName для PullSoundCardMic,
  Flush и DefaultMicSource; SetMainMicDevice перепривязывает ссылки слайсов.
- VfoOverlay: во fly-out AUD ряд вкладок OUT | IN, событие OnAudioInDevice.
- PanafallPanel: проброс списка входных устройств во флаги слайсов и главный.
- Settings: персист in_dev_name.

НЕ ГОТОВО: зависание GUI при выборе на слайсе того же входа, что в настройках
(пул открывает поток вместо шортката на общий FAudioIn). В коде оставлена
временная диагностика [MIC] в stderr. Также подозрение на замедление потока
данных от радио — проверяем сравнением с main.

Со мной в коммите временные тестовые программы PortAudio (other/pa*.lpr).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 21:52:24 +03:00
ew8bakandClaude Opus 4.8 d51f0fb614 feature(calibration): частотная калибровка опорника (ppm) + TO Error в слоте QO-100
Пункт 3 плана. Ручные подгонки остаются: ppm работает ПОД ними, а не вместо —
уход тракта где-то больше, где-то меньше.

- Settings: TCalibration.FreqCalPPM (JSON cal_freq_ppm, +-100 ppm, per-device).
  Смысл знака: «опора выше номинала на N ppm», положительное значение
  поднимает показываемые частоты.
- RadioBackend: виртуальный SetFreqCalPPM.
- HPSDRNetwork: CalFreq(Hz) = Hz / (1 + ppm) в частотном слове — все четыре
  точки (главный DDC, DDC панов, feedback-DDC PureSignal, DUC передачи).
  Тактовый генератор радио менять нечем, поэтому правим запрос.
- PlutoBackend: штатный xo_correction в ad9361-phy (device-attr биндинги
  добавлены в IIOBindings). Номинал XO снимается при коннекте с округлением
  до 100 кГц, чтобы наша же коррекция с прошлой сессии не накапливалась;
  после записи LO переписываются, иначе правка ждала бы следующей перестройки.
  Одна цифра лечит RX и TX на всех диапазонах.
- QO-100: TXvtrEntry.TxLOError (JSON tx_lo_error) + поле «TO Error (Hz)» на
  странице QO-100. Сам транспондер-offset держим стандартным (8089.5 МГц),
  уход тракта правим поправкой; ActiveTXFreqHz учитывает её, подсказка внизу
  страницы считает TX-частоту уже с ней.
- UI-фиксы вкладки Calibration: длинные подсказки переведены на несколько
  строк (WordWrap — TLabel по умолчанию режется по краю группы), заголовки
  таблиц укорочены и не заезжают под кнопку «= S-meter»; RelayoutCalTab
  сбрасывает прокрутку страницы перед перекладкой (SetBounds в TScrollBox
  задаёт координаты относительно прокрученного клиента).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 12:35:46 +03:00
ew8bakandClaude Opus 4.8 6de8c00689 feature(rx): плавный аттенюатор 0..31 дБ в UI, память на диапазон, компенсация уровней
Аттенюатор был только в web и только тремя ступенями (0/-10/-20), а его дБ
никуда не возвращались — при ATT 20 дБ S-метр и спектр уезжали на 20 дБ, и
свежая калибровка уровней была верна лишь при ATT=0.

- RadioController: SetAtten(Db) 0..31 (как умеет железо) вместо индекса *10.
  AttenCalDB входит в SMeterCalFor и DispCalFor — стрелка, спектр, водопад и
  паны компенсируются, шкала dBm остаётся абсолютной (как Thetis/piHPSDR).
  У AD936x аттенюатора нет, там поправка 0 (усиление тракта — hardwaregain).
- Память на диапазон/слот трансвертера по образцу RxGainMode/RxGainDb:
  TBandSettings.AttenDB (band 'atten_db') и TXvtrEntry.LastAttenDB
  ('last_atten_db'); восстановление и отправка в железо — в ApplyBandDSP,
  сбор — в MakeBandSettings/SaveCurrentBand, вход в слот — через B.AttenDB.
  SetAtten сразу кладёт значение в кэш диапазона/слота (JSON пишется общим
  сохранением, а не на каждый шаг ползунка — как у drive).
- MainForm: ряд «ATT [ползунок] NNdB» в RX-блоке левой панели, отступы как у
  VOL; на Pluto ряд скрыт и RX-блок ужимается (LayoutLeftPanel двигает
  панели ниже). Внешние изменения приходят по rfAtten.
- Web: select → ползунок 0..31 с цифрой, attn_idx -> attn_db, cmd attn {db:}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 11:41:21 +03:00
ew8bakandClaude Opus 4.8 366d4c9821 feature(calibration): офсет спектра/водопада на диапазон и на трансвертер
Вторая калибровочная таблица рядом с 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>
2026-07-21 11:16:34 +03:00
ew8bakandClaude Opus 4.8 797bfc2e7c feature(calibration): офсет S-метра на диапазон и на трансвертер, раздельно по железу
Вместо одного офсета на всё устройство — таблицы «на диапазон» и «на слот
трансвертера»; состав таблиц задаёт активное железо.

- 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>
2026-07-21 11:02:04 +03:00
ew8bakandClaude Fable 5 c645b0d1e6 feature(settings): вкладка Calibration — детектор мощности/КСВ, V/A, S-метр
Паритет с Thetis-Enhanced (Det. Cal. / Volts&Amps правки):
- TCalibration в TGlobalSettings (JSON cal_*): модель детектора Alex-моста,
  per-band множитель FWD/REV 0.5..2.0, множитель напряжения, Voff/Sens
  датчика тока, офсет S-метра.
- AlexDetToWatts: константы Thetis computeAlexFwdPower/computeRefPower по
  моделям (ANAN-100 / 200D / 7000DLE-Anvelina Pro 3 / 8000DLE-Orion MkII,
  6m-спецкейс REV). Дефолт = прежняя формула ANAN-100 (поведение не меняется).
- OnHPStatus: FWD и REV через модельную формулу x множитель банда;
  КСВ = (1+p)/(1-p), p=sqrt(RevW/FwdW) (Thetis) вместо отношения сырых ADC;
  напряжение x SupplyVoltCal; ток с калибруемыми Voff/Sens.
- WDSPEngine.SMeterCalDB: офсет в GetSMeterDBm/GetSliceSMeter (главный
  S-метр, slice-флаги, web).
- Выбор модели в комбо подставляет модельные дефолты датчика тока
  (Anvelina Pro 3/7000DLE: 340 мВ / 88 мВ/А — Thetis GetDefaultVoltCalibration).
- Фиксы: гард FLoading при постройке вкладки (SetItemIndex стреляет OnChange
  на программном присвоении -> AV на nil-контролах), FPageCalib/FNavCalib
  в списках SelectPage (вкладка была невидимой).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:35:34 +03:00
ew8bakandClaude Fable 5 7a999c95ee fix(puresignal): цель feedback 22 для Orion MkII + outlier-фильтр calcc
Разбор «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>
2026-07-17 23:01:08 +03:00
ew8bak cd816c54d6 feat(fm): add universal FM RAW decoder output 2026-07-16 18:58:28 +03:00
ew8bakandClaude Fable 5 3acd569da5 feat(puresignal): PureSignal-предыскажение для openHPSDR P2 + кнопки PS/2TON
Протокол (по pihpsdr new_protocol.c): на PTT при PS DDC0<-ADC0 (RX-feedback),
DDC1<-ADC2 (TX DAC loopback), 192к/24бит, sync DDC1->DDC0 (byte 1363=0x02,
interleaved в потоке DDC0), частоты DDC0/1 = TX в HP-пакете, Alex0 bit18
(мост обратной связи). Caps.HasPureSignal для плат с DDCBase=2
(Angelia/Orion/Orion2/Saturn); попутно NumADCs=2 для Angelia.

WDSPEngine: PushPSFeedback (деинтерлив -> pscc(TXA, tx=DDC1, rx=DDC0) из
сетевого потока), PS-параметры calcc в кэше с переприменением после
пересоздания TXA; SetTXTwoTone (PostGen mode 1, 700/1900, 0.49+0.49,
инверсия LSB/CWL/DIGL, мьют mic как TUN).

RadioController: state machine команд (порт Thetis PSForm timer1) в
PureSignalTick @10Гц + auto-attenuate (AttOnTX по уровню feedback, цель
~152, через DUC Specific); SetPSMox и перестройка feedback-DDC на T/R;
маршрутизация DDC0 в pscc из OnDDCIQ; SetTwoTone с гейтами safety.

UI: кнопки PS (ЛКМ = поповер настроек, ПКМ = быстрый toggle) и 2TON в
блоке TX; поповер TPureSignalPopup (дочерний контрол, Wayland-safe):
PS ON/OFF, CAL, RESET, 2TON, лампа/уровень feedback, cals, corr,
GetPk/SetPk, MOX/CAL wait, PH#, Auto-atten, Relax ptol, Pin/Map/Stbl,
Tint, тона двухтональника. Персист ps_* в tx-секции per-device.

Не проверено на железе; Save/Restore коррекции не реализованы.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 11:55:26 +03:00
ew8bakandClaude Opus 4.8 28ae832b71 feat(slices): per-pan tuning wheel and per-slice RX mute on TX
Mouse wheel over an extra panadapter now retunes that pan's primary
slice (first created on that receiver) instead of the main VFO; a slice
flag under the cursor still wins, pan 0 keeps driving the VFO.

Slice flags get a clickable RXM badge: RX audio of the slice is muted
while transmitting (default), so the operator no longer hears himself on
every slice in full duplex. State persists with the workspace.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 22:35:19 +03:00
ew8bak 9f828f747f fix(settings): preserve DMR mode on reload 2026-07-14 20:57:22 +03:00
ew8bak 53a3681618 feat(dmr): add receive frontend and AMBE adapter 2026-07-14 18:38:39 +03:00
ew8bakandClaude Opus 4.8 9dd38e1dd3 feat(pluto): выбор антенного разъёма RX/TX на диапазон (LibreSDR/AntSDR)
Второй разъём у AD936x-плат — это второй КАНАЛ чипа (не порт B первого), и в
1R1T-образе он выбирается мультиплексором AD9361: debug-параметры
adi,1rx-1tx-mode-use-rx-num/-tx-num + биты RX1/RX2, TX1/TX2 регистров 0x3/0x2
(как switch_rfinput.sh/switch_rfoutput.sh прошивки). rf_port_select тут ни при
чём — прошивка его к тому же блокирует.

- IIOBindings: debug-атрибуты + прямой доступ к регистрам.
- PlutoBackend: детект второго разъёма (2R2T-каналы либо мультиплексор + модель
  платы), SetAntPorts(RxPort,TxPort), ApplyRxMux/ApplyTxMux; Caps.HasAntSwitch.
  ⚠ApplyGain(Force): драйвер пропускает запись gain_control_mode, если считает
  режим неизменным, — а у нового канала AGC-регистры не запрограммированы (в
  железе крутится auto-AGC, hardwaregain отвергается EOPNOTSUPP). Форсируем
  перепрог через другой режим после смены разъёма и после ad9361_set_bb_rate.
- RadioBackend: TBackendCaps.HasAntSwitch + virtual SetAntPorts (HPSDR — no-op,
  там антеннами рулит Alex).
- Settings: секция ant936x (разъём RX/TX на диапазон и на слот трансвертера);
  hw-AGC (режим + manual gain) переехал в TBandSettings/TXvtrEntry — теперь он
  свой на каждом диапазоне и трансвертере.
- RadioController: FAnt936x + ApplyAntPorts (RestoreBand/ActivateXvtr/
  StartRunning); ApplyBandDSP восстанавливает hw-AGC диапазона.
- SettingsForm: вкладка Antenna — Alex-таблица (openHPSDR) либо таблица
  разъёмов AD936x по диапазонам/трансвертерам.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 21:26:18 +03:00
ew8bakandClaude Opus 4.8 3566f10191 feat(slices): per-pan дисплей — палитра/gamma/уровни на пан («◑»-поповер)
Кнопка «◑» в шапке каждого пана открывает компактный поповер настроек
отображения (палитра, gamma, ref/range спектра, авто-уровень водопада +
offset/ручные min-max). Модель «глобальный дефолт + override на пан»: пан 0
правит глобальные поля рендера (зеркалятся в контроллер), паны N ставят
DisplayOverride и персистятся в воркспейс раздельно КВ/трансвертер.

- PanDisplayPopup.pas (new): borderless fsStayOnTop форма, гаснет по
  OnDeactivate; Flat*-контролы; крестик «×» закрытия; дим-хинт «правки
  применяются и сохраняются сами»; кнопки «⟳ Reset»/«⇄ Copy to all».
- TPanafallPanel: FBtnDisplay + OnDisplayClick + DisplayOverride;
  PositionHeaderChildren каскадит [×][⧉][◑] справа по Visible.
- MainForm: OnPanDisplayClick/Applied/Reset/ApplyAll + ApplyGlobalRenderToView
  + MarkPanDisplayDirty. При смене палитры/сбросе чистим историю GL-водопада
  (TSpectrumView.ClearWaterfall) — GL хранит уже раскрашенные пиксели.
- Settings/TPanCfg: под-объект 'display' (палитра/gamma/ref/range/wf-уровни),
  пишется только при override.

Глобальными осознанно оставлены разрешение анализатора, MSAA, скорость
водопада, детектор/усреднение (движок/перф, не косметика).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 13:49:19 +03:00
ew8bakandClaude Opus 4.8 543453007e feat(slices): персист панов+слайсов, раздельно КВ/трансвертеры
Сохранение и восстановление панадаптеров, слайсов и раскладки 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>
2026-07-12 23:06:12 +03:00
ew8bakandClaude Opus 4.8 cdbdfec792 feat(bandplan): диапазон OIRT 65-74 МГц в плане Pluto (кнопка перед FM)
VU-план (Pluto) расширен до 12 слотов: OIRT (65..74 МГц, дефолт 69 МГц)
вставлен перед FM. HF-план (openHPSDR) остаётся 11-диапазонным — 12-й слот
пустой, кнопка прячется (RelabelBands). CAT индексный (OIRT = band 08 на
Pluto), SetBand игнорирует пустой слот. Web bandSel строится динамически из
BandPlan; пустые слоты пропускаются.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-10 21:40:05 +03:00
ew8bakandClaude Opus 4.8 ba2a75bb04 feat(dsp): режимы WFM, DIGI-U и DIGI-L
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>
2026-07-10 16:59:27 +03:00
ew8bakandClaude Opus 4.8 e13d610a23 feat(audio): раздельная громкость RX и TX-монитора + фикс синка слайдера
Две сохраняемые громкости: 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>
2026-07-07 16:24:57 +03:00
ew8bakandClaude Fable 5 e260995218 feat(display): скорость водопада с max-hold накоплением + MSAA спектра
- настройка Speed (wf_speed_div 1/2/4/8, Waterfall→Rendering): замедление
  накапливает кадры max-hold'ом в SetWaterfallData — короткие посылки (CW)
  не выпадают из строки; двойная децимация MainForm убрана
  (EffectiveWfInterval, CPU-путь не быстрее полукадровой)
- настройка Anti-aliasing (spec_msaa 1/2/4/8, дефолт 4x, Spectrum→Rendering):
  MultiSampling на GL-контроле спектра; смена в рантайме пересоздаёт
  контекст → ResetGLCache обнуляет текстурные кэши без glDelete

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-04 16:48:55 +03:00
ew8bakandClaude Fable 5 092a202f5f feat(waterfall): палитры Inferno/Turbo + gamma, разрешение до 4096, HiDPI
- 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>
2026-07-04 16:11:58 +03:00
ew8bakandClaude Opus 4.8 5b7eda291e fix(spectrum): сохранение/восстановление зума для трансверторов (XVTR)
Зум был per-band только для HF (TBandSettings). XVTR-слоты хранятся отдельно
(TXvtrEntry) и зум туда не попадал — при входе в трансвертер не восстанавливался.

- TXvtrEntry: LastZoomFactor/LastPanSlider + Default/Load/Save (JSON).
- SaveCurrentBand (XVTR-ветка): пишет зум/пан в слот.
- ActivateXvtr: восстанавливает зум слота + SetZoomPan (re-arm анализатора).
  Покрывает все входы (кнопка, старт по LastXvtr, CAT/web).
- ApplyZoom: кэширует в активный слот — XVTR при активном трансверторе,
  иначе HF-band.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:27:08 +03:00
ew8bakandClaude Opus 4.8 371b2858bd feat(spectrum): zoom + pan для спектра и водопада (WDSP span-clip)
Зум через нативный span-clip WDSP (fscLin/fscHin в SetAnalyzer): clp=0,
видимое окно сужается клипом, при зуме 0 полоса == sample rate (старое
поведение сохранено). Конвенция как в Thetis: ZoomFactor 0=без зума..1=макс.

- WDSPEngine: SetZoomPan + расчёт span-clip, ViewLowHz/ViewHighHz.
- RadioController: GetViewWindow (единая истина видимого окна для оверлеев и
  мыши), ApplyZoom, ZoomIn/Out по лестнице кратностей, ZoomDefault. Якорь
  зума кнопками — на маркере активного VFO. rfZoom.
- Per-band персист зума/пана (TBandSettings + Save/Load/Restore).
- MainForm: маршрутизация оверлеев/кликов через GetViewWindow (покрывает CPU
  и OpenGL), резерв строки PANZOOM_H в layout + сплиттере, rfZoom-рендер.
- WebAdapter: web-спектр/наведение на view-окно (делит те же WDSP-пиксели).
- PanZoomBar (новый): нижняя полоса = линейка полного диапазона (частоты +
  деления), поверх окно-селектор «стекло» (полупрозрачное, объёмная фаска).
  Середина=пан, края=зум, клик мимо=центрировать, двойной клик=сброс.

Сборка ewsdr (--ws=qt6) и ewsdrd — ОК. На железе не проверено.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:18:24 +03:00
ew8bakandClaude Opus 4.8 2d7290e44d feat(cat): Andromeda/G2 front panel support (serial, LED indicators)
Bidirectional Apache Labs Andromeda/G2 front-panel protocol on top of CAT.
Panel logic lives in dedicated units; existing files get only thin hooks.

New units:
- AndromedaMap.pas   — data only: action enums + default button/encoder
  layout tables and LED (ZZZI) numbers (mirroring the Thetis defaults)
- AndromedaPanel.pas — TAndromedaPanel: inbound panel events -> controller
  actions (marshalled via Invoke), state subscription -> ZZZI push via SendProc

Hooks:
- CATEngine: parse ZZZD/ZZZU/ZZZE/ZZZP/ZZZS into generic TCATContext callbacks
- CATAdapter: creates the panel, routes engine callbacks to it, supplies the
  SendProc to the Andromeda port, AddStateListener, ZZZS handshake
- CATSerial: Andromeda flag in port config + HasAndromeda/SendToAndromeda
- RadioController: AddStateListener (multicast, does not steal MainForm's
  OnStateChanged)
- Settings/SettingsForm/MainForm: CATSerialAndromeda[0..3] + persistence + checkbox

First version: serial transport, hardcoded default mapping, LED indicators
(MOX/Tune/CTUN/VFO A-B/Split/NB/NR/SNB/ANF/Squelch). Doc: doc/ANDROMEDA.md.

TODO: default button mapping does not match the physical panel (hardcoded) —
to be fixed later; also ZZMF/LCD text, TCP G2V2 transport, configurable mapping.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:40:10 +03:00
ew8bakandClaude Opus 4.8 3bd37b956e feat: QO-100 self-monitor (RX MUTE) + gate beacon/RX-mute buttons to QO-100
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>
2026-06-23 19:50:30 +03:00
ew8bakandClaude Opus 4.8 2ab1f4f9ac fix: restore per-band FM settings on startup (SQL/CTCSS/step/repeater)
LoadDevice (bulk startup band loader) parsed only freq/mode/filter/AGC/
CTun/center/span and skipped the FM fields, while SaveBand writes them and
LoadBand reads them. So FM squelch, CTCSS, step and repeater shift were
saved on exit but reset to defaults on the next start (band switching kept
working — that path goes through the in-memory band cache, not disk).

Added the missing fmsq_on/fmsq_level/ctcss_on/ctcss_idx/fmstep_on/
fmstep_idx/fmrpt_dir/fmrpt_offset reads to LoadDevice, mirroring LoadBand.
Affects all backends, not just Pluto.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 21:31:38 +03:00
ew8bakandClaude Opus 4.8 3fe5cf3fad feat: QO-100 mode — separate RX/TX LO + dedicated settings page
QO-100 reuses the transverter path (VFO/band-stack/beacon-lock untouched)
with a split LO for the geostationary transponder:
- TXvtrEntry += FullDuplexTx + TxLOOffset (persisted). Reserve the last
  XVTR slot (QO100_SLOT) for QO-100; migration restores the template when
  the slot is unset or has empty offsets (fixes old configs showing zeros).
- XvtrTranslateTX: TX LO = visible - TxLOOffset (no LNB LOError) for QO-100;
  identical to RX translate for normal transverters. Switched all TX-side
  translate calls (controller + MainForm).
- SetDuplex wired (live KeepRXDuringTX); auto display-duplex on QO-100 entry.
- New "QO-100" settings page (LNB LO / transponder offset / downlink edges /
  RX gain / TX ceiling + live RX-IF/TX readout); QO slot hidden from the
  transverter table. Reuses OnXvtrChange — no MainForm wiring needed.
- doc/PLUTO_INTEGRATION_PLAN.md: phase 5 done.

Builds clean (headless + qt6 GUI). Not yet verified on hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:30:23 +03:00
ew8bakandClaude Opus 4.8 d08fc17056 feat: Pluto TX path — attenuation-based power, full-duplex IQ stream
TX path for Pluto/AD936x (RX was already working):
- PlutoBackend: continuous full-duplex TX stream (cf-ad9361-dds-core-lpc)
  with FIFO + push thread; SendDUCIQ upsamples WDSP 192k -> device rate
  (integer, linear interp), 24->16 bit, Q inverted; zeros on underrun.
- Power via output attenuator (ADI-recommended): SetTxAtten writes
  hardwaregain on voltage0(out). Controller maps drive% -> dB
  (PlutoTxAttForDrive/ApplyPlutoTxAtten); -89.75 dB on RX (~off),
  ceiling PlutoTxMaxAttDb on 100% drive.
- Configurable max-att (default -10 dB, ADI-linear) for external amp:
  persisted in global settings, UI spin on PA page, routed through
  controller SetPlutoTxMaxAtt (UI stays decoupled from logic).
- RadioBackend: virtual SetTxAtten (no-op for HPSDR).
- Update doc/PLUTO_INTEGRATION_PLAN.md: phases 3/4/5 + risks.

Builds clean (headless + qt6 GUI). Not yet verified on hardware.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 12:27:49 +03:00
ew8bakandClaude Opus 4.8 40dca01c40 fix: persist CTun center + save VHF/UHF bands (Pluto); QO-100 transverter template
CTun position was lost on band restore / restart, and VHF/UHF bands were
never saved on Pluto.

- SaveCurrentBand: the >61 MHz guard (HPSDR HF-only) skipped saving native
  Pluto VHF/UHF bands → 2m/70cm reverted to defaults. Now HPSDR-only; Pluto
  saves VU-plan bands (FreqToBandIdx guards cross-band pollution).
- Persist DDC center for CTun: new TBandSettings.CenterHz (+ JSON center_hz)
  and TXvtrEntry.LastCenterHz (+ last_center_hz). RestoreBand / ActivateXvtr
  restore the saved center when CTun is on and the offset fits the span,
  instead of forcing center := VFO (which re-centered the receiver).
- UpdateFreqDisplayMax: freq-display upper bound from active transverter's
  FreqEnd (lets QO-100 show 10 GHz past the 6 GHz Pluto cap).
- QO-100 transverter template (slot 2, disabled): downlink 10489.5–10490.0,
  LO 9750. MigrateXvtrTemplate injects it into the first free slot for
  devices that already have a saved xvtr config (template was otherwise
  masked by the loaded slots).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 22:02:01 +03:00
ew8bakandClaude Opus 4.8 24f74357cd feat: Pluto RX hw-gain control + AD9361 cold-init (FIR via libad9361)
- RX hw-gain (Pluto/AD936x): backend SetRxGain(ModeIdx, GainDb) — gain_control_mode
  (manual/fast_attack/slow_attack/hybrid) + hardwaregain. Controller FRxGainMode/
  FRxGainDb, commands SetRxGainMode/SetRxGain/RxGainBy, rfRxGain event, snapshot,
  applied in StartRunning. Persisted per-device (rx_gain_mode/rx_gain_db). HPSDR
  unaffected (backend no-op).
- UI: separate "RF AGC" panel below the (unchanged) WDSP AGC block, shown only for
  Pluto — FAST/SLOW/HYB/MAN mode buttons + manual GAIN slider. LayoutLeftPanel
  reflows lower panels via RelayoutBelowBands; no layout shift on HPSDR.
- Cold-init fix: direct sampling_frequency write doesn't load the AD9361 FIR
  decimation filter, so a cold-booted Pluto showed a wide centre spike that didn't
  track tuning (worked only after SDR Console pre-initialised it). Add optional
  libad9361 binding (dynamic) and call ad9361_set_bb_rate() in ApplySampleRate,
  which configures BBPLL + loads the FIR; falls back to direct write if absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 12:06:59 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 e09bc21bba Fix active-VFO desync; add bandstack-on-VFO-switch and per-XVTR VFO-B
Active-VFO desync (batch 15 follow-up): DoConnectDevice loaded
FActiveVfo from settings but never re-rendered the highlight (it was
drawn once in FormCreate while the field was still 0) nor re-tuned the
receiver. Result on reconnect with B last-active: the engine (spectrum,
filter band, mouse tuning) ran on VFO B while the UI showed A active.
Now the connect path brings both the receiver and the highlight to the
actually-active VFO via SetActiveVfo, and RestoreBand's final retune
follows the active VFO (A -> ApplyVfoA, B -> SetVfoB) instead of always A.

Bandstack on VFO switch: switching the active VFO to one parked on a
different ham band now restores that band's DSP state (mode/filter/AGC/
CTUN/FM). Extracted ApplyBandDSP (the DSP half of RestoreBand, without
the VFO frequencies) and reused it from both RestoreBand and SetActiveVfo.
VFO frequencies stay independent (each VFO carries its own band);
continuous tuning across a band edge still does not recall (band button
only) as on real radios. SaveCurrentBand no longer writes a VFO frequency
into a band's memory unless that VFO is actually within the band, so a
40m VFO-A no longer pollutes 30m's stored VfoA.

Per-transverter VFO-B: TXvtrEntry gains LastFreqB (load/save/default +
range clamp). ActivateXvtrBand restores VFO B, Deactivate/SaveAllAndExit
persist it, and SetVfoB clamps to the transverter range like SetVfoA.
The connect-time guard that forced VFO A in XVTR is dropped since B is now
a valid, in-range frequency there too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 12:45:11 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 f12d2a69ad Phase 3 (batch 8): route desktop Mode/Filter through controller commands
- FILT_* таблицы (BW/DEF/DEV/COUNT) перенесены MainForm -> Settings.pas
  (нужны контроллеру для headless-выбора фильтра); UI-подписи FILT_*_NAMES
  остались в MainForm (ссылки разрешаются через uses Settings).
- ApplyModeFilter (чистый DSP) перенесён в TRadioController; все вызовы в
  MainForm -> FController.ApplyModeFilter.
- Контроллер: ApplyModeDefaults (дефолтный фильтр/девиация под FMode,
  зеркало state-части UpdateFilterButtons) + наполнены команды SetMode
  (FMode + дефолт + DSP SetMode/TUN + ApplyModeFilter + band cache) и
  SetFilterIdx (FFilterBW по таблицам режима + ApplyModeFilter + band cache).
- BtnModeClick/BtnFilterClick — тонкие обёртки к командам; рендер
  (StyleButton + UpdateFilterButtons + SyncSpecViewFreq) в
  OnControllerState(rfMode/rfFilter).

OnModeFilterSelect (VFO overlay), SyncWebMode/SyncWebFilter (web/CAT),
RestoreBand пока зовут FController.ApplyModeFilter инлайн — мигрируют
в следующих батчах (как и в прошлых: web/overlay позже).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:34:35 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 aff17aa3a6 Add Audio tab toggle for transceiver built-in speaker
Adds a "Send audio stream to radio (built-in speaker)" checkbox on the
Settings → Audio tab. It is a single switch for the radio's built-in
speaker:

- ON  — RX audio is streamed to the radio via the DDC Audio packet
        (port 1028, 64 L/R pairs per packet) and the speaker is unmuted
        (High Priority byte 1400 bit1 = 0).
- OFF — speaker is muted (byte 1400 bit1 = 1, ANAN-8000DLE IO1 output)
        and no audio stream is sent.

The float->int16 conversion and 64-pair packet accumulation live in
THPSDRNetwork (SendSpeakerAudio/SetSpeakerAudio), next to SendDDCAudio;
MainForm only reads the setting and forwards the audio. State is
persisted per-MAC (send_audio_to_radio, default off) and re-applied on
each radio start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-02 08:55:36 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 655d242d02 Add Spectrum gradient fill toggle in settings
Mirror the existing Wideband gradient fill option for the main spectrum:
a "Spectrum gradient fill" checkbox on the Display settings tab gates the
under-curve gradient in TSpectrumView.DrawSpectrum. The curve, grid,
filter band and VFO markers always draw; only the gradient is optional.

Persisted as spectrum_fill in settings JSON (default True, preserving
current appearance). Wired through Settings -> SettingsForm visibility
event -> MainForm -> TSpectrumView.FillSpectrum, same path as
WidebandFill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-29 15:24:33 +03:00
Uladzimir Karpenka 4f127a30ad fix macOS build 2026-05-28 15:59:49 +03:00