Commit Graph
100 Commits
Author SHA1 Message Date
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 827902f3e2 fix(xvtr): правки в трансвертере больше не травят память КВ-диапазона
При входе в слот трансвертера FCurrentBand не меняется — он продолжает
указывать на тот КВ-диапазон, с которого зашли. Поэтому всё, что помнится
по диапазону, из трансвертера писалось в чужую ячейку: модуляция, слот
фильтра, режим и порог АРУ, CTUN. Поставил в трансвертере FM — 40 метров
запомнили FM.

Развилка «мы сейчас в трансвертере?» в проекте уже была, но стояла только
у двух писателей из семи — у шагового аттенюатора и у зума. Их правили
когда-то по симптому, отчего баг и возвращался: затыкали поле, а не класс.

Заведена XvtrSlotActive, через неё идут все оставшиеся писатели. Слот
трансвертера соответствующие поля уже имел (LastMode, LastFilterIdx,
LastAGCMode, LastAGCTop, LastCTun) — они просто не заполнялись живыми
правками, только снимком на выходе из слота.

SpanHz в кэше оставлен как есть: поле помечено легаси, при восстановлении
диапазона его никто не читает.

Уже испорченные записи конфига правка не лечит: чтобы починить диапазон,
надо встать на него, выставить нужную модуляцию и уйти — SaveCurrentBand
перезапишет ячейку.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:33:22 +03:00
ew8bakandClaude Opus 5 45d10698ee fix(cw): запрет передачи действует и на кейер прошивки (RX-only XVTR, DoNotTx)
Аудит трансвертерного режима против дефектов этой сессии нашёл ту же болезнь,
что и всё остальное: защита жила только на пути через софтовый MOX.

Бэнд с DoNotTx (Alex) и RX-only слот трансвертера проверялись тремя копиями
кода — в SetMOX, SetTune и SetTwoTone. С кейером в прошивке ни одна из них не
исполняется: на приёмном трансвертере замыкание ключа выходило в эфир, а там
на выходе обычно вход конвертера, а не антенна.

Три копии сведены в TXProhibited; CWTXActive её учитывает, поэтому байт 5
обнуляется и кейер разоружается там, где передавать нельзя. Передача текста
гейтится тем же условием.

Вооружение сделано самовосстанавливающимся: SyncCWKeyer идемпотентен (пара
сравнений плюс ранний выход внутри SetCWKeyerArmed) и вызывается ещё и из
разбора HP-статуса. Разоружить кейер может заход в RX-only слот, переход на
запрещённый бэнд или правка Alex; перечислять такие точки поимённо — гарантия
однажды пропустить очередную, на чём уже дважды обожглись за сегодня.

Остальное в трансвертере проверено и чисто: сдвиг pitch переживает
XvtrTranslateTX (включая split-LO QO-100), множитель мощности слота и
VHF-калибровка уже входят в CalcDriveByte и потому уезжают в байт 345,
OC-выходы идут за ключом через RadioKeyed, ActivateXvtr толкает верную частоту
DUC. Инвертирующие трансвертеры не поддержаны нигде в проекте — это давнее
общее ограничение, не регресс телеграфа.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 14:09:05 +03:00
ew8bakandClaude Opus 5 e87ca6b186 fix(hpsdr): окно повторов Specific-пакетов — только со старта, не на каждую пере-сборку
RebuildDDCSpecific сбрасывал FResendCount, а этим счётчиком keepalive-поток
управляет окном повторов: пока он меньше 100, каждые 500 мс переотправляются
DDC Specific и DUC Specific. Окно задумано как стартовый костыль — порты
1025/1026 на железе открываются только после Run=1.

Сброс из пере-сборки делал окно рецидивирующим: любая переконфигурация на
живой сессии заводила ещё десять пере-латчей DDC Specific в течение пяти
секунд. Вызывают её вход и выход PureSignal из передачи (то есть каждый MOX,
когда PS вооружён), добавление и удаление панадаптера, смена sample rate.
Пакет конфигурирует все DDC разом, поэтому пере-латч на ходу трогает и
главный приёмник — этой же операцией когда-то был получен мусорный поток на
доп. панах. С PS и 2TON выходило по двадцать пере-латчей на передачу, что
похоже на давний открытый пункт про всплески водопада при 2TON.

Счётчик сбрасывается теперь только по Run=1. Размен: повторы заодно
маскировали потерю UDP-пакета с конфигом посреди сессии, и переконфигурация
стала одним пакетом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:55:19 +03:00
ew8bakandClaude Opus 5 c6d1e409ff fix(cw): реле T/R и антенна за ключом прошивки + залипание HW-PTT
Отвязав FTransmitting от телеграфа, я оставил в приёмной конфигурации всё,
что бэкенд считает от состояния передачи: CalcAlex0 бит 27 (реле T/R),
выбор антенны (TxAnt вместо RxAnt), bypass/Ext1 и CalcOCBits, которым
ключуется внешний усилитель. Прошивка гнала несущую, пока плата фильтров
стояла на приём, и приёмник видел собственный передатчик — на водопаде это
широкополосные полосы в моменты манипуляции.

PushNetworkState отдаёт в UpdateState RadioKeyed, и он же вызывается на
фронтах ключа. PTT в кадре этим не поднимается (он живёт в SendPTT), так
что манипуляцией по-прежнему владеет FPGA. Выдержка RadioKeyed поднята до
hang + 250 мс: по ней теперь щёлкает реле, и щёлкать оно обязано раз на
передачу, а не на каждую посылку. FTuning из RadioKeyed убран — при TUN
и так взведён FTransmitting, а в разборе SetTune флаги гаснут не разом.

Второе: телеграфная ветка разбора статуса заканчивается Exit, и падающий
фронт HW-PTT обработать было некому. Если в эфир увёл именно HW-PTT, пока
источником передачи был не телеграф, а телеграф включился уже после, то
передача залипала навсегда — FTransmitting взведён, Alex в TX, PTT не
снимается. Подтверждено на железе: всплески появлялись после переключения
передачи на слайс и оставались после возврата на главный.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:54:58 +03:00
ew8bakandClaude Opus 5 0a1a1ee3af fix(cw): первый прогон на железе — частота, мощность, индикация, полоса
Семь дефектов, вскрытых проверкой в эфире. Общий корень у большинства: с
кейером в прошивке FTransmitting не поднимается вовсе — передачей владеет
FPGA, — а половина проекта считала этот флаг признаком эфира.

Обрыв посылки после первого элемента. При break-in железо само поднимает
T/R на каждую посылку, HPS_PTT это отражает, и разбор статуса гонял по
фронту SetMOX(True/False); снятие MOX гасило CWX. Теперь в телеграфе фронт
HPS_PTT не управляет передачей. Без этого и работа манипулятором
пересобирала бы передающий тракт на каждой точке.

Обрыв по «key hit» переведён на фронт лепестка: на плате без ключа входы
могут стоять в единице, и проверка уровня убивала бы передачу сразу.

Посылка уходила по центру дисплея, а не на VFO. StartRunning ставил DUC на
центр DDC; до первой перестройки VFO или до первого MOX там и оставалось.
Без CTUN центр совпадает с VFO, поэтому баг и дожил до телеграфа, где MOX
не бывает. SetRunAndFreq получает TX-частоту, SetSliceTarget толкает кадр,
когда перестраивают слайс-источник. Правило: DUC обязан стоять правильно
всегда, а не только на передаче.

Мощность ниже, чем на TUN: уровень (байт 345) клался в кадр только при
FIsTransmitting. Введён SetCWKeyerArmed — пока кейер вооружён, уровень
лежит в каждом HP-кадре. Там же пересчитывается FDriveLevel, иначе до
первого касания ручки в железо уходил ноль.

Индикация не показывала передачу: события rfTransmitting в телеграфе не
бывает, поэтому блок рендера не выполнялся ни разу. ApplyHPStatus шлёт его
сам на фронтах RadioKeyed («железо в эфире» = передача или замыкание ключа
прошивкой, выдержка 8 точек и не меньше 500 мс). На RadioKeyed переведены
S-метр, красная полоса главного VFO и TX-бейджи флагов вместе с красной
полосой слайса на панадаптерах.

Полоса на спектре рисовалась как SSB: TXSignedEdges обязана отдавать для
CW голосовую боковую (через bp0 идёт тон pitch при настройке), поэтому
экранные кромки развязаны в TXFilterEdgesHz — занимаемая полоса
манипуляции симметрично несущей. Тот же класс, что был у ЧМ.

F1..F8 молчали, пока не откроешь окно памяти: обработчик упирался в
проверку лениво создаваемой формы. Текст берётся из настроек.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 13:14:38 +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
ew8bak 0ec120c2d5 Merge feature/tx-profiles: TX-профили, подвкладки Transmit, заводские EQ ESSB, привязка профиля к «флаг + модуляция» + пласт UI/DPI 2026-08-09 22:06:15 +03:00
ew8bakandClaude Opus 5 6195c80ce3 fix(tx): полоса ЧМ на передаче — занимаемая полоса, а не кромки bp0
TXFilterEdgesHz была тонкой обёрткой над TXSignedEdges, то есть отдавала
в отрисовку кромки аудио-полосовика перед модулятором. У SSB/CW/DIGI/AM
это то же самое, что полоса вокруг несущей, а у ЧМ — нет: FM RAW рисовался
как (20..7000), WFM как (20..15000), то есть полоса уезжала вверх от
несущей и визуально «переключалась на USB».

Теперь у экранных кромок своя семантика — Карсон ±(девиация + верхний срез
аудио) для FM/WFM/FM RAW, остальные режимы как были. TXSignedEdges и
SetTXABandpassFreqs не тронуты: bp0 остаётся портом Thetis, в эфире ничего
не меняется. Попутно 20.0/7000.0 из трёх мест FM RAW сведены в
RAW_AF_LOW/RAW_AF_HIGH.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:09:31 +03:00
ew8bak 7c9064659b ui(main): align left panel sliders 2026-08-05 16:51:43 +03:00
ew8bak 19babef4d7 ui(status): use system font 2026-08-05 16:11:57 +03:00
ew8bak a0f4b044db ui(spectrum): theme sample rate overlay 2026-08-05 16:07:25 +03:00
ew8bak b70daa9dbf ui(main): fix DPI layout and theme switching 2026-08-05 15:55:03 +03:00
ew8bak 998cb3f0fe ui(beacon): add flat memo with overlay scroll 2026-08-05 15:06:48 +03:00
ew8bak 0396eeed49 ui(channels): redesign editor and fix DPI scaling 2026-08-05 14:50:15 +03:00
ew8bak 5120d8d21d ui(beacon): redesign decoder diagnostics form 2026-08-05 14:35:39 +03:00
ew8bak 960f221179 ui(settings): replace native vertical scrollbar 2026-08-05 14:10:20 +03:00
ew8bak 1d04973d6f fix(discovery): use system fonts and correct DPI scaling 2026-08-05 13:49:23 +03:00
ew8bak b7b9119339 feat(ui): add hover overlay scroll for left panel 2026-08-05 13:44:12 +03:00
ew8bak 94bb15bbaf fix(ui): scale Pluto left-panel relayout 2026-08-05 13:24:54 +03:00
ew8bak 907c2c05c3 ui: use system font in flat controls 2026-08-05 13:20:03 +03:00
ew8bak bd057a7849 fix(settings): prevent duplicate DPI scaling 2026-08-05 12:42:56 +03:00
ew8bak 9047dc586d ui(settings): align cards and spacing 2026-08-04 21:02:15 +03:00
ew8bak adf5c5016e ui(settings): refine transmit and hardware layouts 2026-08-04 20:47:55 +03:00
ew8bak 612bc73687 ui(settings): add responsive page layout 2026-08-04 20:36:17 +03:00
ew8bak 304e574592 ui(settings): normalize layout and system fonts 2026-08-04 20:23:33 +03:00
ew8bakandClaude Opus 5 4e6703e590 fix(xvtr): TUN идёт от глобального уровня, мощность слота — множитель
При включённой опции TUN в трансвертере брал мощность слота ВМЕСТО глобального
TUN Level, из-за чего ручка «Tune level» на вкладке Transmit в трансвертере
была мёртвой, а настройка уходила в эфир на мощности слота (у 70cm и QO-100 —
все 100 %).

Теперь уровень всегда от TUN Level, а слотовая TX Power работает множителем:
Pos = TUNLevel * слот/100 — та же формула, по которой в трансвертере уже
масштабировался обычный drive. Смысл поля становится единым: «сколько мощности
трансивера разрешено этому трансвертеру», и к настройке это относится так же,
как к передаче.

Галка сохранена (кому нужен неотмасштабированный TUN), но переименована в
«Scale TUN by XVTR power» — прежнее название описывало снятое поведение.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:40:59 +03:00
ew8bakandClaude Opus 5 199d0ffa85 ui(tx): треугольник пилюли профиля выровнен по надписи
Треугольник центрировался по прямоугольнику пилюли, а текст — по TextHeight,
который включает выносные элементы вниз. В подписи одни заглавные, они сидят
в верхней части бокса, поэтому треугольник визуально проваливался: по пикселям
текст занимал строки 7..13, треугольник 11..15.

Считаем от метрики самой надписи (TxtY + TextHeight div 2 - 2), а не от бокса,
чтобы выравнивание пережило смену шрифта или кегля. После правки текст 7..13,
треугольник 8..12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:30:54 +03:00
ew8bakandClaude Opus 5 249881a579 fix(tx): профиль по модуляции — детерминированное правило + пробелы вызова
Три связанных правки по отзывам с эфира.

1. Активный профиль однозначно определяется парой «TX-источник + его
модуляция»: ячейка таблицы заполнена — её профиль, пуста — базовый (первый в
списке). Прежняя «точка возврата» (запомнить, что было активно до автоматики)
ломалась в самом частом случае: заходя в FM, оператор обычно УЖЕ имел активным
профиль FM с прошлого захода, точка возврата записывала его же, и выход в USB
ничего не менял. Плюс она жила только в сессии и после перезапуска не работала
вовсе. Поле FTXProfPreAuto удалено.

Ручной выбор (дропдаун TX, список настроек, пилюля флага) дополнительно
ЗАПОМИНАЕТСЯ за текущей парой — это и есть обещанное «профиль помнится по виду
модуляции»: раньше в таблицу писала только пилюля, а выбор дропдауном следа не
оставлял. В DMR и FMRAW автоматика активный профиль не трогает вовсе.

2. Смена диапазона и вход/выход трансвертера меняют режим главного в
ApplyBandDSP (FMode := B.Mode) МИМО SetMode, поэтому автоматика там не
вызывалась и при переходе КВ↔трансвертер оставался профиль прошлого режима.
Зовём и оттуда. Заодно: FDSPEngine.SetMode насильно ставит FTXMode в режим
главного, а SetMode контроллера следом возвращает режим источника —
ApplyBandDSP этого не делала, и передача со слайса после смены диапазона
уезжала в чужую модуляцию.

3. Кнопка выбора TX-профиля гаснет, когда режим ИСТОЧНИКА передачи DMR или
FMRAW (в DMR передатчика нет, в FMRAW профиль обходится); открытый список
закрывается. Гейт по источнику, а не по главному: с главным в DMR можно
передавать со слайса. Смена режима на слайсе теперь тоже пересчитывает
доступность TX-контролов — это чинит и MOX/TUN/PS.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 19:23:49 +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 f320ef1bcf fix(tx): полоса передающего слайса на доп. панах рисовалась по RX-ширине
CalcSliceBandX берёт кромки TX из FTXEdge* ТОЙ вьюхи, где живёт слайс, а
PushFilterEdgesToView отдавала их только FSpecView (пан 0). У панов N поля
оставались нулями, ветка TX не срабатывала, и слайс на передаче показывал
свою RX-полосу независимо от профиля и TX-фильтра.

Кромки TX уходят во вьюхи всех панов, кадр каждого помечается грязным.
RX-кромки главного туда не отдаём: на панах N главный VFO не рисуется.

Подтверждено на железе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:20:52 +03:00
ew8bakandClaude Opus 5 8f95795bbb ui(tx): вкладка Transmit целиком на английском + сетка группы Microphone
Страница была единственной с русскими подписями — переведены подсказки всех
четырёх подвкладок, надпись «Profile drive: N %» и диалоги профиля (New /
Rename / Delete / Factory / лимит). Строковых литералов с кириллицей в
SettingsForm больше нет.

Группа Microphone приведена к сетке страницы: подписи в колонке PAD, поля в
колонке CX (комбобокс Mic jack и спин Mic gain теперь в одной колонке),
Connector переехал во вторую пару колонок к Mic gain — на не-Orion он скрыт и
дыры не оставляет, поэтому отдельная третья строка не нужна (176 → 134 px).
Тумблеры boost/bias/PTT расставлены с равным шагом; пара «Line In gain»
занимает место чекбокса boost и заканчивается до «Mic bias».

Английские подписи длиннее русских — все проверены рендером формы offscreen,
в группы вписываются.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 18:08:59 +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 c8781bee6a fix(tx): полоска TX-фильтра перерисовывается при смене профиля и правке Low/High
Кромки TX-полосы в SpectrumView обновляла только PushFilterEdgesToView,
которую звали из rfMOX/rfMode/rfFilter. Смена TX-профиля (rfTXProfile) и
правка Low/High во вкладке Transmit перешивали WDSP, но вьюха продолжала
рисовать старую полосу до следующего MOX или смены режима — на передаче
эти события не приходят вовсе.

Зовём PushFilterEdgesToView из RenderTXProfile и из OnTXSettingsChange
(кромки берутся из движка уже со знаком боковой, TXSignedEdges). Кадр
форсируем через MainForm.FSpectrumDirty — он же покрывает TX со слайса,
где FTXOverlay=False, а полоса слайса читает те же FTXEdgeLo/Hi.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 14:50:06 +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
ew8bak 24890ca8a7 Merge feature/filter-edges: произвольные кромки фильтра, редактор, drag, TX-полоска
Фильтр главного приёмника стал таблицей (Name, Low, High) на режим: 10
пресетов + VAR1/VAR2, знаковые кромки. Поповер-редактор по ПКМ на кнопке
фильтра, перетаскивание края полосы на спектре (Alt = симметрично), полоса
при передаче рисуется по TX-фильтру, CAT ZZFL/ZZFH на главном порту,
энкодеры #4/#5 Андромеды крутят High/Low раздельно.

Попутно устранён дубль таблицы знака боковой (RX-конвенция жила и в
контроллере, и в отрисовке) и потеря информации при рисовании полосы слайса
(пара кромок схлопывалась в одну ширину).

Проверено на железе: 25521a2 + фикс ce43116 (слот VAR терялся при рестарте
в режиме трансвертера).
2026-08-04 13:35:57 +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 c95620fb23 fix(ui): DPI-масштабирование SettingsForm/DeviceForm + общий DpiScale
Вся раскладка окон, построенных кодом (SettingsForm, DeviceForm), была
захардкожена в пикселях под неявные 96 DPI — на Windows 125-200% или
HiDPI-десктопах контролы физически не росли вместе с укрупнившимся
шрифтом. Добавлено масштабирование SetBounds через MulDiv(V,
Screen.PixelsPerInch, 96) на каждой листовой точке потребления
(const-блоки раскладки не трогались).

Общий DpiScale вынесен в новый юнит DpiUtils.pas — убрана дублированная
копия одноимённого приватного метода в 9 классах (FlatCheckBox,
FlatComboBox, FlatListBox, FlatSpinEdit, FlatFloatSpinEdit, FlatEdit,
FlatRadioButton, FlatPopupMenu, SettingsForm, DeviceForm) и инлайн-MulDiv
без обёртки в MainForm.pas/PanafallPanel.pas.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-29 16:05:39 +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
ew8bak b6d2256416 Merge feature/slice-cat: CAT-порт на слайс + Auto TX, аудио in/out на слот слайса 2026-07-29 12:31:02 +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 8611898ad1 fix(tx): WFM/FMRAW bp0 = аудио-полоса режима, не SSB-фолбэк
TXSignedEdges для WFM падал в else→сырую магнитуду (200,3100) и душил
вещательное аудио до телефонной полосы ещё ДО ЧМ-модулятора (у того свой
AF 20–15000). Добавлены ветки: WFM→(20,15000)=RX AF WFM, FMRAW→(20,7000)=
RX AF raw. Теперь TX-bp0 совпадает с RX-АУДИО-фильтром (истинная симметрия),
а не с РЧ-шириной канала FILT_WFM_BW/FILT_RAW_BW (это селективность RX,
другая величина). Развязка TX от RX для SSB/AM/CW не тронута.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 11:40:17 +03:00
ew8bakandClaude Opus 4.8 f425071461 fix(tx): ширина TX-полосы независима от RX-фильтра (модель Thetis)
Убрано RX→TX зеркало: ApplyModeFilter больше не тащит края RX-фильтра в TX
(FDSPEngine.SetTXFilter удалён и из вызова, и как метод движка — был
единственный вызов). Раньше TX bp0 писали два источника с разной шириной:
ApplyModeFilter (RX-ширина FFilterBW) и ApplyTXChainSettings/SetTXFilterFull
(TX-фильтр 200/3100) — на MOX побеждал TX-фильтр, между оверами держалась
RX-ширина, ширина прыгала.

Теперь TX-полосу задаёт ТОЛЬКО TX-фильтр из настроек (через TXSignedEdges),
независимо от RX. RX LSB 5.0k больше не раздувает излучаемый сигнал. База под
ESSB: расширение TX-фильтра в Setup сразу даёт нужную полосу передачи.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 21:28:01 +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 b7e277e72b Merge feature/pan-grid-two-col: грид панов из двух колонок + FM-сетка на доп. панадаптерах
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:22:49 +03:00
ew8bakandClaude Opus 4.8 02288292ff feat(pans): сетка FM-каналов и шаг тюнинга на доп. панадаптерах
Доп. паны не имеют своего FMode — режим берётся со слайсов пана:
PanHasFMSlice(PanId) = есть слайс в FM/DMR/FMRAW. По нему SyncPanZoomUI
выставляет View.FMGridStepHz = FM_STEP_HZ[FFMStepIdx], как SyncSpecViewFreq
делает для главного пана (сеттер сам гасит кэши спектра и линейки).

Тюнинг слайса привязан к той же сетке:
  • колесо над слайсом/доп. паном — было зашито 12500 и только DMR/FMRAW,
    теперь FM_STEP_HZ[FFMStepIdx] во всех трёх канальных режимах (гейт FFMStepOn);
  • PanClickTune — там была та же зашитая 12500, теперь шаг режима целевого
    слайса, как в DoSpectrumClick главного пана.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 19:22:30 +03:00
ew8bakandClaude Opus 4.8 c857aa9d4b fix(slices): squelch слайса гейтится режимом — уход из FM больше не глушит
Юзер: на слайсе включил 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>
2026-07-22 12:16:49 +03:00
ew8bakandClaude Opus 4.8 3bdcfa0674 ui(pans): флаг A появляется вместе с первым доп. панадаптером
Доп. панадаптер приходит со своим слайсом и сразу рисует его флаг, а
главный приёмник оставался без флага — [A] показывался только когда слайс
создавали на самом пане 0 (инвариант «панель скрыта ИЛИ есть слайсы B+»).

TPanafallPanel: третье слагаемое инварианта — MainFlagForced, выставляется
хозяином. MainForm.ResizeSpectrumPanels после LayoutPanStack ставит
FPan.MainFlagForced := PanCount > 1 и зовёт UpdateMainFlagVisibility; это
единая точка — добавление/закрытие пана, restore персиста и STOP все идут
через пересчёт раскладки. Сворачивание левой панели (MainFlagPinned) на
это не влияет: пока жив хотя бы один доп. пан, флаг A остаётся.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 12:11:57 +03:00
ew8bakandClaude Opus 4.8 6c38777c58 ui(pans): грид из двух колонок — пан 0 и доп. пан бок о бок
Раньше «▦» имел смысл только при >=2 доп. панах (с одним грид совпадал со
стеком) — основной и единственный дополнительный приёмник могли стоять
лишь друг под другом. Теперь при ОДНОМ доп. пане грид = две колонки во всю
высоту: пан 0 слева, доп. справа, между ними тот же вертикальный сплиттер.
Wideband при этом остаётся сверху во всю ширину PanelRight, обе колонки
начинаются под ним (StackTop=AWideH, пан 0 раскладывается с ATopOffset=0,
шапки панов уходят в свои колонки — Pan0HeaderH над wideband не резервируется).

MainForm: ExtraPanCount/PanGridTwoCol; FPanColShare расширен на индекс 0
(пан 0 — тоже колонка), доля персистится через Cfg.Pans[0].ColShare — ключ
col_share в Settings уже был, теперь пишется для всех панов; отдельная ветка
в LayoutPanStack (горизонтальные сплиттеры спрятаны, кламп 15% ширины на
колонку); vsplit-хендлеры ищут левую колонку downto 0 в two-col и нормируют
доли с учётом пана 0; PanSplitMouseDown/Up гейтятся not PanGridTwoCol;
кнопка «▦» видна уже при одном доп. пане.

Отдельно исправлен sync-гард в тике дисплея: он сверял PbWideband.Width с
FPan.SpectrumWidth, а в two-col пан 0 — половина ширины при wideband во всю,
поэтому условие было вечно истинным и ResizeSpectrumPanels вызывался каждый
кадр — раскладка пересчитывалась под курсором (вертикальный сплиттер не
тянулся) и зря сбрасывались битмапы. Теперь wideband сверяется с
PanelRight.ClientWidth — его настоящим инвариантом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 12:08:06 +03:00
ew8bakandClaude Opus 4.8 ef444e6607 Merge feature/slice-audio-input: устройство ВВОДА на каждом слайсе
Вкладки OUT/IN в fly-out AUD флага, mic-пул с refcount (переключение
TX-слайса = смена указателя, без Close/Open — не даёт щелчков), слайс в
FM RAW можно назначать источником передачи, персист in_dev_name.
Плюс удобства AUD-панели: открывается на выбранном устройстве и
прокручивается колесом мыши.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 10:22:23 +03:00
ew8bakandClaude Opus 4.8 8b5faaf986 chore(audio): убраны временные зонды PortAudio
other/padump.lpr, patest2.lpr, patest3.lpr писались для диагностики
зависания при выборе входа на слайсе (двойное открытие устройства, дамп
PA-списка). Баг закрыт как невоспроизводимый, зонды в сборку не входят и
ниоткуда не вызываются — при надобности достаются из истории (4aa180e).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 10:21:39 +03:00
ew8bakandClaude Opus 4.8 c986232891 ui(vfo): колесо мыши прокручивает список устройств в AUD
VfoOverlay запоминает прямоугольник списка (FAudListRect) и обрабатывает
колесо над ним (HandleMouseWheel): шаг = одна строка. Если курсор в этой
зоне, событие считается обработанным и не уходит в спектр — частота не
съезжает даже когда список целиком помещается и крутить нечего.

PanafallPanel.DispatchFlagsMouseWheel перебирает главный флаг и флаги
слайсов (как остальные Dispatch*), MainForm.FormMouseWheel зовёт его по
всем панам первым делом после дисплеев VFO; координаты берутся через
PbSpectrum.ScreenToClient(Mouse.CursorPos), поэтому работает и в pop-out.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:29:32 +03:00
ew8bakandClaude Opus 4.8 fb1c263460 ui(vfo): AUD-панель открывается на выбранном устройстве
При раскрытии fly-out AUD (и при переключении вкладок OUT/IN) список
теперь прокручивается к текущему устройству — выбранная строка ставится
примерно в середину видимого окна, а не приходится искать её стрелками.
Ручной скролл при этом не сбрасывается: push списков от контроллера
(SetAudioDevices/SetAudioInDevices) позицию не трогает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-22 09:25:41 +03:00
ew8bakandClaude Opus 4.8 c69d537852 feature(tx): слайс в FM RAW можно назначать источником передачи
TX-бейдж на флаге слайса был заблокирован и для DMR, и для FM RAW, хотя тракт
FM RAW передавать умеет: SetTxSlice отбрасывает только DMR, а SetTXMode уводит
FM RAW в WFM с плоской девиацией. RX-only остаётся один DMR (нет AMBE-кодера).

- VfoOverlay.TxSelectionAllowed: запрет сузился до DMR.
- RadioController.SetSliceMode: если режим меняют у слайса, который сейчас
  источник TX, передающий тракт перестраивается под новый режим. Без этого
  выбор FM RAW на уже выбранном TX-слайсе оставлял в эфире прежнюю модуляцию
  до следующего клика по бейджу. На переходах в/из FM RAW заодно пересматривается
  источник модуляции (внешний PCM) со сбросом кольца входа — как в главном
  SetMode.

Заодно снята временная диагностика [MIC] (MicDbg и её вызовы) из mic-пула:
зависание при выборе на слайсе входа из настроек больше не воспроизводится.

Проверено на железе: TX со слайса в FM RAW работает. Подробное тестирование —
следующим шагом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 23:01:59 +03:00
ew8bak 93ab03f1eb Merge branch 'main' into feature/slice-audio-input
# Conflicts:
#	ewsdr.lpi
2026-07-21 22:44:22 +03:00
ew8bakandClaude Opus 4.8 66c9fa63b9 fix(settings): профиль устройства уезжал в нулевой MAC при старте по AutoStart
Настройки персистятся секцией по MAC радио, но ResolveDevice брал MAC только из
списка найденных дискавери. У сохранённых устройств поля MAC не было вовсе, и
START по AutoStart/CONNECT без дискавери шёл с MAC 00:00:00:00:00:00 — грузился
и сохранялся чужой профиль. Пользователь видел «настройки сбросились»: выключался
wideband, менялся sample rate, терялись банды и паны, причём в зависимости от
того, нажимал ли он перед стартом DISCOVER.

- DeviceStore: у TSavedDevice поле MAC + persist в hpsdr_devices.ini (MAC=..),
  FindSavedByAddr/SetSavedMac (пишет ini только при изменении), MacIsZero.
- RadioController.ResolveDevice: saved-ветка восстанавливает и MAC.
- RadioController.EnsureDeviceMac: добор неизвестного MAC — найденное в этой
  сессии → ini → короткий служебный поиск в сети (700 мс, unicast на DirectIP,
  OnDeviceFound временно снят, чтобы не трогать список устройств в UI).
- ConnectDevice: добор MAC до Connect, после успеха MAC запоминается за
  сохранённым устройством, так что следующий AutoStart идёт без пробника.

Проверено на железе (ANAN 172.16.2.200): в hpsdr_devices.ini появился
MAC=04:91:62:FD:7B:86, секция нулевого MAC больше не создаётся, старт по
AutoStart и старт после DISCOVER дают один и тот же профиль.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 22:43:32 +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
ew8bak 317e9b732c Merge feature/calibration-tab: калибровки уровней и частоты (паритет с Thetis)
Вкладка Calibration + всё, что нужно, чтобы шкала dBm и частота были
абсолютными на обоих бэкендах.

- Детектор мощности/КСВ Alex-моста по моделям ANAN, per-band множитель
  FWD/REV, калибровка напряжения и тока питания.
- Офсеты S-метра и спектра/водопада — раздельными таблицами на диапазон и на
  слот трансвертера, по активному железу: у openHPSDR HF-план, у Pluto
  VHF/UHF-план, секции JSON ключуются по MAC.
- Регулируемое усиление входа больше не двигает шкалу: шаговый аттенюатор
  openHPSDR (ставший плавным 0..31 дБ с ползунком в RX-блоке и памятью на
  диапазон/слот) и hardwaregain AD936x, который в режимах AGC железо крутит
  само.
- Частотная калибровка опорника (ppm): у HPSDR — частотное слово DDC/DUC, у
  Pluto — xo_correction. Ручные LO Error / TO Error трансвертеров остаются
  сверху; в слоте QO-100 сам транспондер-offset теперь стандартный, а уход
  тракта правится отдельной поправкой.

Проверено на железе (openHPSDR и Pluto).
2026-07-21 12:38:48 +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 a95292d2c0 feature(pluto): компенсация hardwaregain в уровнях + живое усиление в блоке RF AGC
На Pluto шкала dBm ничего не значила: усиление тракта 0..73 дБ входит в
показания напрямую, а в режимах AGC железо крутит его само, и картинка
«дышит» вместе с ним.

- RadioBackend: виртуальный RxGainNowDb (TELEMETRY_NONE = регулируемого
  усиления нет, как у openHPSDR с его аттенюатором).
- PlutoBackend: в MANUAL отдаёт FGainDb (точно, без опроса железа), в режимах
  AGC — hardwaregain из телеметрического потока (читается там же, где rssi,
  ~2 Гц, не блокируя UI).
- RadioController: AttenCalDB -> FrontEndCalDB, общее понятие «регулируемое
  усиление входа»: openHPSDR +FAtten (аттенюатор режет), AD936x -hardwaregain
  (усиление поднимает). Постоянная часть тракта (LNB, кабель, конвертер)
  остаётся за таблицами калибровки на диапазон/трансвертер.
- UI: в блоке RF AGC при режиме != MANUAL вместо надписи «AGC» — фактическое
  усиление («~38dB», тильда = измеренное), ползунок ездит сам. Видно, что
  цифра гуляет, а уровни на спектре стоят.

Ограничения: FAST AGC крутит усиление быстрее опроса (2 Гц), для стабильной
шкалы нужен MANUAL или SLOW; шумовая полка при смене усиления немного едет —
это физика (input-referred NF), компенсацию проверять по сигналу.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-21 11:50:28 +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
ew8bak ced42fbac9 fix(hpsdr): prevent false connection lost on tick race
The RX timeout check sampled GetTickCount64 on the UI thread while the UDP receive thread could update FRXLastPktTime a millisecond later. Both values are QWord, so subtracting the newer packet tick from the older UI tick underflowed to a very large value and falsely satisfied the 3-second timeout.

Snapshot the packet timestamp once for each timeout check and require NowTick >= LastPktTick before calculating the elapsed interval. The initial-packet timeout uses the same snapshot. This preserves real timeout detection while ignoring a packet timestamp that is marginally newer than the UI tick.

Diagnostics confirmed that DDC UDP packets, callbacks, and HP status remained continuous during the reported failures; the issue was local timeout arithmetic rather than network loss or radio stream interruption.
2026-07-21 00:13:35 +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
ew8bak a7e154b959 Merge feature/puresignal: PureSignal-предыскажение для openHPSDR P2 (Orion MkII, паритет с Thetis) 2026-07-18 10:44:01 +03:00
ew8bakandClaude Fable 5 bd2a6bc2ea ui: плашка ADC OVERLOAD — общий образ CPU/GL без пер-кадрового жора
AlertOverlay переписан в общий builder: BuildAlertImage строит RGBA-образ
плашки (попиксельная альфа, Canvas один раз на своём битмапе). GL грузит
образ текстурой (кэш как был), CPU блендит кэш в raw-кадр — убраны
canvas-путь в кадре и полный проход восстановления альфы битмапа.
GL-плашка теперь 1:1 с CPU (было: упрощённый бокс по центру), позиция —
правый верхний угол в обоих рендерах. Удалена мёртвая подсветочная линия
(перекрывалась рамкой).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 10:36:23 +03:00
ew8bakandClaude Fable 5 e337f29314 ui(2ton): читаемые IMD-маркеры на спектре
- сводка «IMD3 −NN dBc» слева от полосы фильтра (не прячется за
  SampleRateOverlay), шрифт 9→11
- маркеры пиков: все подписи в dBm как в Thetis (dBc только в сводке),
  все зелёные (оранжевый = вычисленная сводка), подпись центрирована
  над своим кружком (не наезжают у соседних тонов)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 09:59:50 +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 6a9aebc23c fix(tx): enable protocol 2 CIC compensation filter 2026-07-17 18:51:07 +03:00
ew8bak 6197ed0aee Add flat FM RAW transmit path 2026-07-16 20:33:39 +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
ew8bak a84de32294 fix(pluto): enable antenna switch for stock model 2026-07-15 20:17:21 +03:00
ew8bak 26ea96fdd2 add innosetup ewsdr_dmr.dll 2026-07-15 10:21:23 +03:00
ew8bak cc75ebf0d4 delete libewsdr_dmr 2026-07-15 10:11:47 +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 fa5a95ca5d fix(dmr): avoid stale and mislabeled talkgroups 2026-07-14 21:30:41 +03:00
ew8bak 7c4b024186 feat(dmr): show stable status on receiver slices 2026-07-14 21:23:24 +03:00
ew8bak 8b28986fec fix(ui): show DMR status without replacing PLL 2026-07-14 21:05:59 +03:00
ew8bak 9f828f747f fix(settings): preserve DMR mode on reload 2026-07-14 20:57:22 +03:00
ew8bak a880ed0e6c fix(ui): close DMR step panel layout gap 2026-07-14 20:54:38 +03:00
ew8bak 62e663baf5 feat(dmr): default tuning step to 12.5 kHz 2026-07-14 20:51:10 +03:00
ew8bak 5cfd585eea chore(dmr): remove test and tool directories 2026-07-14 20:46:07 +03:00
ew8bak 831045f37d chore(dmr): remove diagnostic capture probes 2026-07-14 20:42:48 +03:00
ew8bak d794805081 fix(dmr): block MOX and tune on receive mode 2026-07-14 20:35:20 +03:00
ew8bak e5451b7843 fix(dmr): prevent TX selection on receive slices 2026-07-14 20:31:21 +03:00
ew8bak 53aef502e9 fix(ui): sync main slice controls with panel 2026-07-14 20:28:10 +03:00
ew8bak b7dd57c64f fix(dmr): raise decoded voice level 2026-07-14 20:23:49 +03:00
ew8bak 9755378586 fix(dmr): normalize voice audio and lock bandwidth 2026-07-14 20:14:50 +03:00
ew8bak bac0b7612e feat(dmr): decode independent receiver slices 2026-07-14 20:08:49 +03:00
ew8bak 9e2f8c7743 fix(dmr): choose timing phase by sync variance 2026-07-14 19:56:36 +03:00
ew8bak 22505d4df7 fix(dmr): center discriminator eye per sync 2026-07-14 19:47:55 +03:00
ew8bak a633bf57cd fix(dmr): calibrate slicer from sync symbols 2026-07-14 19:38:56 +03:00