Commit Graph
240 Commits
Author SHA1 Message Date
Uladzimir KarpenkaandClaude Opus 4.8 492b29bea1 Phase 3 (batch 19): move TUN (tune tone) into the controller
SetTune now carries the TUN engine logic: the DoNotTx/RXOnly safety
gates, enabling the WDSP PostGen tone before TX, the TUN-level drive
(CalcDriveByte sees FTuning), driving TX on/off via SetMOX, and the
network re-push on exit. MainForm.ApplyTUN becomes a thin wrapper
(mirrors FWebClientActive, calls SetTune); the BtnTUN style render moves
to OnControllerState(rfTuning). BtnMOXClick still exits tune via
ApplyTUN(False).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:40:01 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 603e0850cf Phase 3 (batch 18): move MOX (TX core) into the controller
SetMOX now carries the TX engine/network logic: DoNotTx/RXOnly safety
gates, drive recalc, the PTT relay sequence (relay-before-PTT on TX-on,
PTT-before-relay on TX-off), mic-source selection (web/HW-PTT/default),
KeepRXDuringTX, SetTXRun, and the non-DUP TX->RX FlushRX + post-TX mute.
New controller fields FWebClientActive (mirrored from the web server in
the thin ApplyMOX wrapper) and FWebMicActive (moved from MainForm), plus
DefaultMicSource. UI render (BtnMOX style, FSpecView TX overlay/mode,
grid, FDUCPendingCount reset) moved to OnControllerState(rfTransmitting).

Also fix a pre-existing status-bar bug: web TX was labelled 'TX radio
mic' (the old if/else folded any non-soundcard source into radio); now
a case shows 'TX web mic' for txmsWeb.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:35:54 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 c249b46b17 Phase 3 (batch 17): move CTUN center scroll (SetCenter) into the controller
SetCenter was a Phase-1 skeleton; filled it with the CTUN view-scroll
logic: set FCenterFreq, shift the demodulator by (active VFO - center),
push a full HP frame, and emit Changed(rfCenterFreq). The UI renders via
OnControllerState (invalidate ruler cache + deferred spectrum redraw, since
a drag emits many events).

Both entry points now call FController.SetCenter: the desktop CTUN drag
(DoSpectrumDrag) and the web center scroll (SyncWebCenter). Removes the
duplicated inline shift/network logic from both.

Side effect / latent fix: the web center scroll previously only called
UpdateState without SendFullHP (the desktop path did both), so a web scroll
did not push the new DDC center to the radio immediately. Routing it through
PushNetworkState now sends the full HP frame like the desktop path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 14:00:46 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 831e5c3a31 Phase 3 (batch 16): move XVTR activate/deactivate into the controller
ActivateXvtrBand/DeactivateXvtr followed the Band pattern: the state +
DSP + network now live on TRadioController; the UI renders via events.

- ApplyXvtrToNetwork moved to the controller (XVTR enable bit / DisablePA
  / RX antenna). MainForm.ApplyXvtrToNetwork is a thin delegate.
- ActivateXvtr builds a TBandSettings from the XVTR slot and runs it
  through the shared ApplyBandDSP (mode/filter/AGC/CTUN/FM), then sets
  VFO B (range-clamped) / VFO A / center / drive and pushes to the radio.
  Render is driven by Changed(rfXvtr) + Changed(rfActiveVfo). The slot has
  no stored filter bandwidth, so FilterBWFor(mode, idx) derives it from
  the per-mode table without changing the index.
- DeactivateXvtr saves the slot via SaveCurrentBand (its XVTR branch
  already writes the whole slot) and exits; the HF return (RestoreBand +
  final active-VFO retune) stays in the UI wrapper.
- SetXvtrBand is now a real dispatcher (>=0 activate, <0 deactivate).
- rfBand render is XVTR-aware (clears HF band buttons while in XVTR);
  added an rfXvtr render case for the XVTR button highlight.
- The thin UI wrappers keep only wideband-view + web push. ~150 lines of
  inline logic removed from MainForm; all callers (web/channel/band-click/
  startup) are unchanged and go through the wrappers.

Also fixes a latent filter-restore bug surfaced by the XVTR round-trip
(transverter -> HF -> transverter): the rfMode render calls
UpdateFilterButtons, which resets the non-FM filter to the mode default.
That reset belongs to a mode change (the controller already applies it via
ApplyModeDefaults), not to a passive render, so it clobbered the filter
restored from the band/XVTR cache. rfMode now preserves FFilter/FFilterBW
around UpdateFilterButtons. This also fixes the same loss on plain HF band
switches for non-FM modes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 13:49:31 +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 24102343ea Phase 3 (batch 15): move band save/restore into the controller
Per the architecture decision, band state lifecycle belongs on the
controller (it operates entirely on controller state + engines;
persistence is already delegated to Settings.pas). A separate unit would
need friend access to ~20 fields + engines — worse coupling.

- MakeBandSettings, SaveCurrentBand: moved verbatim (pure over controller
  state; SaveCurrentBand handles the XVTR-slot vs HF band-cache split and
  delegates the write to FSettings). MainForm.SaveCurrentBand is now a thin
  delegate (22 call sites unchanged); MakeBandSettings removed from MainForm.
- RestoreBand: state + DSP application moved to the controller; rendering is
  driven by per-field Changed events (rfBand/rfMode/rfFilter/rfAGC*/rfFM*/
  rfCTun/rfVfoB). The FM filter-index sanitize (which UpdateFilterButtons
  did inline as a render side-effect) is extracted into a pure
  SanitizeFilterForMode so the DSP ApplyModeFilter still runs on the
  sanitized BW (preserves original ordering). Fixes the same AGC index-cast
  bug as earlier batches (now ApplyAGCToEngine).
- MainForm.RestoreBand is a thin wrapper: ResetWfAvgBuf (band-switch-only,
  not per-tune) + controller.RestoreBand + ApplyVfoA(FVfoA) for the final
  tune with channel orchestration (auto-CTCSS / pre-channel drive).
- Added rfFMStep render case (BtnFMStep on/off was styled ad-hoc).

Builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:37:02 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 a8a192a3a3 Phase 3 (batch 14): route VFO swap / A>B / B>A through controller
Filled the three VFO-transfer commands (the skeletons were also backwards:
VfoACopyB stored A:=B etc).

- VfoSwap: swaps A/B, retunes the receiver to the active VFO freq
  (center=freq, shift 0), HF band-detect, network push. Fires
  rfVfoA/rfVfoB/rfBand. UI handler is thin (+ ruler-cache invalidate, since
  a swap is a large freq jump).
- VfoACopyB (A>B): delegates to SetVfoB(FVfoA) — store B:=A, retune when B
  active, render via rfVfoB.
- VfoBCopyA (B>A): copies B->A; when A active goes through SetVfoA, else
  just fires rfVfoA. The desktop B>A handler still calls ApplyVfoA for the
  active-A case to preserve channel orchestration (auto-CTCSS / pre-channel
  drive), matching the original (A>B never had that path).

Net simplification; builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:17:50 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 38947113f5 Fix wheel over a VFO display tuning both that VFO and the active one
TFreqDisplay.DoMouseWheel already tunes its own VFO per-digit when the
wheel is used over it. But the form-level FormMouseWheel also fired for the
same event (Cocoa routes the wheel to the form too) and tuned the active
VFO, so scrolling over the VFO B display changed both B (via the widget)
and A (via the form handler).

Skip FormMouseWheel when the cursor is over either FreqDisplay; let the
widget handle per-digit tuning there. Uses Mouse.CursorPos for the hit-test
(the event's MousePos is not in screen coordinates on Cocoa). The form
handler still tunes the active VFO over the spectrum/waterfall/empty areas.
Pre-existing bug.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 21:07:12 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 9f7a48f89a Phase 3 (batch 13): route active-VFO switch through controller command
- TRadioController.SetActiveVfo(Idx): switches the active RX VFO and retunes
  the receiver to its frequency. CTUN handling preserved: if the new VFO's
  freq is visible in the current window, only the demodulator shift changes
  (DDC/network untouched); otherwise CTUN is dropped and the window centers
  on the freq. HF band-detect + network push as before. Fires
  Changed(rfActiveVfo) (+ rfBand / rfCTun when those change).
- OnControllerState(rfActiveVfo): UpdateVfoDisplay (freq displays + RX/TX
  button highlights) and a full spectrum/waterfall/ruler redraw.

MainForm.ActivateVfo is now a thin delegate; all callers (VFO select/TX
buttons, freq-display clicks, web ActivateVfo) go through it unchanged.
Band-detect keeps the historical no-drive-recalc behavior. Builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:47:35 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 af85ddece6 Fix TX following active RX VFO instead of the TX selector
The TX VFO is chosen independently of the RX VFO via the TX-A/TX-B buttons
(FSplitTxB: False=A, True=B); UI highlights TX-A when not split. But the TX
frequency and TX overlay used FActiveVfo in the non-split branch, coupling
TX to the active RX VFO. Result: with RX on B and TX-A selected, the radio
still transmitted on B (TX overlay also drew on B).

Make the TX VFO depend solely on FSplitTxB in all three spots:
ActiveTXFreqHz and both TXVfoIndex assignments (live update + ApplyMOX).
Now RX=B / TX=A works as the UI indicates. Pre-existing bug, not a refactor
regression. Builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:40:40 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 9d48f26dd8 Fix mouse wheel always tuning VFO A regardless of active VFO
FormMouseWheel quantized from FVfoA and always called ApplyVfoA, so the
wheel tuned VFO A even when VFO B was active. Pre-existing bug (the wheel
handler never branched on FActiveVfo), surfaced while testing VFO B.

Quantize from ActiveVfoFreq and route to SetVfoB when B is active.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:31:04 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 eb10283c85 Phase 3 (batch 12): consolidate VFO B tuning into controller command
The "set VFO B" engine block was duplicated inline across three tune paths
(DoSpectrumClick, DoSpectrumDrag, FreqDispBChanged), each subtly different
(drag had no band-detect/ruler; click had no drive recalc).

- TRadioController.SetVfoB(Hz): always stores FVfoB; when B is the active
  VFO, retunes the receiver (DDC=VFO B, no CTUN, shift 0), HF band-detects
  (FCurrentBand + drive recalc -> Changed(rfBand)) and pushes to the radio;
  when B is inactive (split, listening on A) it only stores the freq for TX.
  Fires Changed(rfVfoB).
- OnControllerState(rfVfoB): renders FreqDispB and, when B active, the
  spectrum/ruler (deferred via FSpectrumDirty, matching the rfVfoA path).

The three call sites collapse to a single FController.SetVfoB(...) each;
FreqDispBChanged is now a one-liner. Behavior unified: click/FreqDispB drop
from sync DrawSpectrum to deferred (consistent with VFO A click-to-tune,
imperceptible); drag gains band-detect + ruler refresh; click gains drive
recalc on band change. Net -23 lines.

Left inline (not identical): DoWidebandClick VFO B (extra XVTR clamp +
wideband-specific render) and SyncWebFreq (uses SetRunAndFreq, the web/run
network path, not UpdateState+SendFullHP). Builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:25:55 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 908c02d08a Phase 3 (batch 11): route VFO A tuning core through controller (keystone)
ApplyVfoA is the keystone of the heavy class — click-to-tune, drag, wheel,
web freq and RestoreBand all funnel through it, and it was the most
UI-coupled method. Split per the long-standing plan: pure frequency engine
into the controller, channel orchestration + render left in UI.

- TRadioController.CalcDriveByte: moved verbatim (it was already pure over
  controller state — reads only band/xvtr cal, TUN/drive%, PA max, no
  widgets). MainForm.CalcDriveByte is now a thin delegate (17 call sites
  unchanged). Needed by SetVfoA's band-detect.
- TRadioController.SetVfoA(Hz): XVTR clamp, FVfoA, CTUN shift/center with
  filter-edge center-scroll (exposes FLastVfoScrolled for the waterfall),
  HF band-detect (FCurrentBand + drive recalc -> Changed(rfBand)), FM
  auto-repeater (UpdateRptAutoState), PushNetworkState, Changed(rfVfoA).
  TuneActiveBy (the Arduino encoder path) now fully tunes the radio.
- PushNetworkState made public (command + UI pre-channel-drive both use it).

MainForm: ApplyVfoA is now thin — calls SetVfoA, then the channel-only
orchestration (auto-CTCSS reset, pre-channel drive restore via TrkDrive
widget + re-push), then CheckChannelActive. The auto-RPT reset block was
dropped: SetVfoA's UpdateRptAutoState already yields the same net state and
running it after SetVfoA would have wrongly cleared a freshly auto-set
MINUS (ordering hazard the memory flagged). Render (FreqDispA, spectrum,
ruler, band buttons) moved to OnControllerState(rfVfoA/rfBand), so every
caller — encoder/web/CAT included — updates the display for free.

Builds clean, links. Click-to-tune/drag/wheel/band still go through this
path; VfoB / ActiveVfo / Band(RestoreBand) / Xvtr still pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:17:45 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 6edcf8bdf4 Phase 3 (batch 10): route sample-rate/span change through controller
Span ≡ sample rate in this app; OnSampleRateSelect (overlay dropdown) is a
fairly self-contained heavy-class item.

- TRadioController.SetSampleRate(Hz): sets FSampleRate/FSpanHz + band cache,
  recreates the WDSP channel (ChangeSampleRate = Close+FlushQueue+Open),
  pushes the new DDC rate to the radio, and restores full DSP state
  (mode/volume/filter/NR/NB/SNB/ANF/AGC-top/AGC/shift).
- Fixes the same latent AGC cast bug as earlier batches: the inline restore
  used SetAGC(TWDSPAGCMode(FAGCMode)) (index cast); now goes through the
  shared ApplyAGCToEngine mapping.

MainForm: OnSampleRateSelect is a thin wrapper (global-settings SaveGlobal
stays in UI). OnControllerState(rfSampleRate) renders the rate overlay and
recomputes AGC display lines (UpdateAGCLines needs UI spectrum width).
Dropped the cosmetic ProcessMessages pre-paint. Startup paths that set
FSampleRate directly + RecreateDSPEngine left as-is. Builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:03:59 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 967e7cb7b1 Phase 3 (batch 9): route FM repeater shift through controller commands
FM Rpt is the first FM-Rpt heavy-class item (XvtrTranslate/ActiveTXFreqHz
blocker already lifted in d895800). Following the established pattern:

- TRadioController.SetFMRpt(Dir): explicit direction, clears auto-active,
  pushes network state; rendered via OnControllerState(rfFMRpt).
- SetFMRptOffset(Hz): updates offset, re-pushes when RPT engaged.
- UpdateRptAutoState: 2 m auto-MINUS logic moved into controller, fires
  Changed(rfFMRpt) instead of inline StyleButton.
- Extracted private PushNetworkState helper (full HP frame); SetCTun now
  reuses it (dedupes the UpdateState+SendFullHP block).

MainForm: Btn/EdFMRpt handlers are thin wrappers (toggle decision +
SaveCurrentBand stay in UI); UpdateRptAutoState delegates to controller;
ApplyFMRpt removed. OnControllerState(rfFMRpt) restyles both buttons and
sets the offset edit text only when unfocused (avoids disrupting typing).
Direct FFMRptDir writes in band/channel/xvtr restore paths left as-is
(migrate with their own commands later). Builds clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 19:56:56 +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 05bb68ac58 Fix ruler lag on click-to-tune and VFO change
ApplyVfoA (общий путь click-to-tune/wheel/VFO/band) синхронно дёргал
PbSpectrum.Invalidate, но не PbRuler.Invalidate — линейку обновлял лишь
таймер через NeedsRulerRedraw-гейт, отсюда "не всегда" при CTUN off.
Invalidate только планирует перерисовку (не тяжёлый рендер), а DrawRuler
дешёвый, поэтому дёргаем PbRuler.Invalidate сразу рядом с PbSpectrum.

Явная инвалидация Ruler из RestoreBand убрана как избыточная — band
идёт через ApplyVfoA, теперь единый источник правды там.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:15:03 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 af1352b1e6 Fix ruler not redrawing on CTun toggle and band switch
Ruler (PbRuler) — отдельный paintbox, перерисовывается только по
PbRuler.Invalidate. Дискретные события его не дёргали:
- CTun toggle (batch 7): OnControllerState(rfCTun) рисовал спектр/водопад,
  но не линейку. Добавлен InvalidateRulerCache + PbRuler.Invalidate.
- Смена диапазона: RestoreBand -> ApplyVfoA, а ApplyVfoA намеренно
  откладывает рендер на таймер (FSpectrumDirty) ради wheel/drag — что
  для оффлайна/без RUN не перерисовывало линейку. Добавлена синхронная
  инвалидация Ruler в конце RestoreBand (дискретное событие, флуда нет).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:09:21 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 2e4cd3cc65 Phase 3 (batch 7): route CTun toggle through controller command
Первая команда тяжёлого класса. TRadioController.SetCTun несёт логику
движка/сети (band cache, при выключении — центрирование на VFO + SetShift(0)
+ UpdateState/SendFullHP); все нужные поля и хелперы XvtrTranslate/
ActiveTXFreqHz уже в контроллере. BtnCTunClick — тонкая обёртка
(SetCTun(not FCTun)); рендер (StyleButton/SyncSpecViewFreq/перерисовка
спектра+водопада) ушёл в OnControllerState(rfCTun).

Прямые записи FController.FCTun в путях band restore / ActivateVfo
оставлены — мигрируют со своими командами (Band/VFO).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:49:18 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 d895800f62 Phase 3 (heavy prep): move XvtrTranslate/ActiveTXFreqHz into controller
Перенос частотных хелперов (общий блокер для Vfo/Mode/Band/CTun/TX/FM-Rpt)
в TRadioController как чистых read-only функций над состоянием. Все
константы (CFG_XVTR_COUNT, MODE_FM, RPT_MINUS/PLUS) уже доступны через
Settings/WDSPEngine/FMRepeater в uses контроллера. MainForm.XvtrTranslate
и MainForm.ActiveTXFreqHz стали тонкими обёртками-делегатами — ~40 мест
вызова в UI/сетевом слое работают без изменений.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:28:33 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 fd145b28f6 Phase 3 (batch 6): route FM squelch + CTCSS through controller commands
The user plans to expose FM over web/CAT later, so FM controls get the
same controller treatment now — future web/CAT FM commands plug straight
into these.

- Relocate CTCSS_COUNT/CTCSS_TONES from MainForm to FMRepeater (a shared
  FM unit MainForm already uses) so TRadioController can drive CTCSS on the
  DSP. CTCSS_NAMES stays in MainForm (UI dropdown labels). MODE_FM is
  already visible to the controller via WDSPEngine.
- SetFMSquelch/SetFMSquelchLevel apply FM squelch (mode-gated) to the DSP;
  SetFMCTCSS/SetFMCTCSSTone clamp and apply CTCSS. All fire OnStateChanged.
- OnControllerState restyles the SQ/CTCSS buttons, syncs the squelch slider
  + label, and the CTCSS tone dropdown. Handlers/SetFMCTCSSTone call the
  commands (manual CTCSS still clears the auto-active flag in the handler).

FM repeater stays for the heavy batch — it needs XvtrTranslate/
ActiveTXFreqHz moved into the controller. ApplyFMSquelch remains for
band/mode re-apply paths (RestoreBand, XVTR).

Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:15:46 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 7d3570346f Phase 3 (batch 5): route FM step through controller command
FM step is the only FM control exposed to another frontend (web:
SyncWebFMStep in, FWebServer.FMStepIdx out), so it's the FM param with
real cross-frontend value.

- TRadioController.SetFMStep clamps and fires OnStateChanged(rfFMStepIdx).
- OnControllerState updates the step dropdown, pushes FMStepIdx to the web
  server, and sets the FSpecView FM grid step (when in FM mode).
- MainForm.SetFMStep is now a thin wrapper over the command, so existing
  callers (band restore, dropdown select, web sync, XVTR restore) are
  unchanged.

FM squelch/CTCSS/repeater are intentionally left as-is: they are UI-only
(no web/CAT/daemon exposure) and already operate on controller state
fields, so routing them through commands adds ceremony without value for
the swappable-UI/daemon goals; CTCSS would also need CTCSS_TONES relocated
to a shared unit and repeater needs the network helpers (XvtrTranslate/
ActiveTXFreqHz) that belong to the heavy-command batch.

Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:10:44 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 ad21516d48 Phase 3 (batch 4): route RX step attenuator through controller
SetAtten clamps, applies to the network (SetStepAtten), and fires
OnStateChanged; SyncWebAttn calls it. No desktop widget exists for the
attenuator, so there's no OnControllerState case — but state and the web
snapshot now flow through the single command path like everything else.

Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:00:04 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 b3e4360a5d Phase 3 (batch 3): route Volume, AGC-top, WfAGC/WfNF through controller
First sliders through the controller, exercising the FSyncingFromController
re-entrancy guard:
- SetVolume/SetAGCTop clamp, apply to the DSP engine, update band cache,
  and fire OnStateChanged. SetAGCTop reuses ApplyAGCToEngine (the unified
  AGC index->enum mapping), fixing the same bad TWDSPAGCMode(index) cast
  that TrkAGCChange/SyncWebAGCTop used when re-applying AGC on level change.
- OnControllerState updates the slider/label/spectrum (rfAGCTop also
  refreshes the FSpecView AGC line, now consistent for web-initiated
  changes too) and the FSpecView waterfall flags (rfWfAGC/rfWfNF).
- TrkVolumeChange/TrkAGCChange (guarded), SyncWebVolume/AGCTop/WfAGC/WfNF,
  and ApplyWfAGCNF all call the commands.

Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 14:58:03 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 f76533f209 Phase 3 (batch 2): route AGC mode through controller command
TRadioController.SetAGCMode now owns the index->WDSP mapping, applies to
the DSP engine, and updates the band cache, then fires OnStateChanged.
OnControllerState restyles the AGC button group. BtnAGCModeClick,
OnVfoOverlayAGCChange, and SyncWebAGC all call the command.

Fixes a latent web bug: SyncWebAGC used TWDSPAGCMode(index) directly,
mapping the UI index (0=FAST..4=OFF) onto the wrong enum (0=OFF...), so
web AGC selections set the wrong DSP mode. The button/overlay path used
the correct AGCModes[] array; unifying on the command applies that to web
too. Buttons/overlay behavior unchanged.

Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:33:45 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 ff42889fa1 Phase 3 (batch 1): route NR/NB/SNB/ANF through controller commands
Apply the slice pattern to the noise-filter controls:
- TRadioController.SetNR/SetNB/SetSNB/SetANF now clamp and apply to the DSP
  engine, then fire OnStateChanged.
- OnControllerState refreshes the matching button (UpdateNR/NB/SNB/ANF).
- Click handlers, SyncWebNR/NB/SNB/ANF, and OnVfoOverlayDSPChange all call
  the commands instead of poking fields/widgets and the DSP directly.

UI clicks, the web client, and the VFO overlay now share one path; the
web client already renders these fields in applyState, so UI->web syncs.
Builds clean (cocoa); behavior unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:20:40 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 b577a0d67a Phase 3 (slice): event-driven UI plumbing + Mute through controller
Vertical slice to establish the Phase 3 pattern before replicating it
across all commands: logic moves into controller commands, the UI becomes
event-driven, and frontends call commands instead of poking widgets.

- TRadioController.SetMute now carries the real logic (state + FDSPEngine.
  SetMute), then fires OnStateChanged(rfMute).
- MainForm subscribes via OnControllerState (controller -> UI). It restyles
  BtnMute from controller state. FSyncingFromController guards against
  re-entrancy when a programmatic widget change would retrigger OnChange.
- BtnMuteClick -> FController.ToggleMute; SyncWebMute -> FController.SetMute.
  The button/web no longer own the mute logic or the restyle.

Proves the command/event/refresh loop end-to-end (UI click and web both
route through the controller). Remaining commands follow this pattern.
Builds clean (cocoa); behavior unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:01:39 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 2cf01036a3 Phase 2: move widget-stored state into TRadioController
NR/NB modes, SNB/ANF flags, and drive % were stored only in the widgets
themselves (Btn*.Tag, TrkDrive.Position) — the last hard blocker to a
headless/daemon mode, since without those controls the state had nowhere
to live and CAT/Web read them directly.

- NR/NB: BtnNR.Tag/BtnNB.Tag -> FController.FNRMode/FNBMode (Integer).
- SNB/ANF: BtnSNB.Tag/BtnANF.Tag (0/1) -> FController.FSNB/FANF (Boolean).
- Drive %: TrkDrive.Position -> new FController.FDrivePercent; the slider
  is now pure input (TrkDriveChange pushes its value into the controller),
  while FDriveLevel stays the derived hardware byte.

Buttons/sliders become pure view+input: click handlers and UpdateXButton
read/write the controller field; CAT getters, the web snapshot, and
GetSnapshot all source state from the controller, not the widgets.

No behavioral change. Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:48:51 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 63eb2c701f Phase 1: extract TRadioController (state + engine ownership)
Decouple MainForm from CAT/Web by introducing a headless core,
TRadioController, as the single home for radio state and engine objects.
This is groundwork for swappable UIs, a daemon (Web-only) mode, and a
future hardware control panel — each becomes a frontend/adapter over the
same controller API.

RadioController.pas (new):
- Radio state + engine objects (Network/DSP/Audio/Settings/Channels) as
  public fields named exactly as in MainForm, so wiring is mechanical.
- Command API: absolute Set* plus relative *By/Up/Down (for encoders).
- OnStateChanged(field) notifications and GetSnapshot for frontend sync.
- Invoke/OnInvoke (TUIInvokeEvent) thread-marshalling seam (GUI uses
  TThread.Synchronize; daemon supplies its own).
- Commands are scaffolding for now (update field + fire event); engine
  logic stays in MainForm and migrates in later phases (TODO-wiring).

MainForm.pas:
- Owns FController; creates it first in FormCreate, frees it last in
  FormDestroy. Engines are still created/freed by MainForm into the
  controller's fields (ownership move is a later phase).
- ~1400 FXxx references redirected to FController.FXxx (implementation
  only); dead field declarations removed from the interface.

No behavioral change — pure state relocation. Builds clean (cocoa).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:43:01 +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
ew8bak d8584323ac Revert "Add WFM (broadcast FM) demodulator support"
This reverts commit e52e32dc20.
2026-06-01 14:40:39 +03:00
ew8bakandClaude Opus 4.7 e52e32dc20 Add WFM (broadcast FM) demodulator support
Wires the new wdsp WFM API into the engine and adds a WFM preset to the
FM filter row (200 kHz BW / 75 kHz deviation). Also fixes WDSP_SAM
constant which was 12 — that slot is now RXA_WFM in the updated enum.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 22:40:24 +03:00
ew8bakandClaude Sonnet 4.6 83ec28f997 Prevent OS sleep and display blanking while SDR is running
Add PowerInhibit.pas with InhibitSleep/UninhibitSleep:
- Windows: SetThreadExecutionState (kernel32, no child process)
- macOS: caffeinate -d -i -s -w <PID> (auto-exits on crash via -w flag)
- Linux: systemd-inhibit with kill-0 poll loop (releases within 5s on crash)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 21:33:30 +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 2ab02d7681 Store WDSP wisdom file in app config dir alongside settings JSON 2026-05-28 22:16:57 +03:00
ew8bakandClaude Sonnet 4.6 4f281ce400 Channel memory: apply and restore TX power, fix auto-clear after band switch
- ApplyChannel now sets TrkDrive from Ch.Power; saves pre-channel level in
  FPreChannelPower (works around TrkDriveChange clearing the field on assignment)
- Power is restored when tuning away from channel (CheckChannelActive) or on
  first VFO movement after returning to band with no active channel (ApplyVfoA)
- Manual drive slider move clears FPreChannelPower so restore is skipped

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:52:54 +03:00
ew8bakandClaude Sonnet 4.6 1175e42516 Add channel memory: CH button, dropdown, editor form, smart CTCSS/RPT auto-clear
- New ChannelStore.pas: TChannel record + JSON persistence (channels.json)
- New ChannelsForm.pas: full channel editor (list + fields, pre-fills from current radio state)
- CH button in RX block: left-click opens dropdown, right-click opens editor
- Active channel: button highlights with channel name, deactivates on VFO move
- ApplyChannel: auto-switches XVTR/HF band based on channel frequency; saves old
  band state before switching to prevent cache corruption
- SaveCurrentBand: skip save when VFO > 61 MHz (prevents HF cache corruption
  from VHF channel application without XVTR)
- DeactivateXvtr: save full XVTR state (was only saving LastFreq)
- CTCSS/RPT from channel marked as auto: cleared automatically when tuning away
  from channel frequency; survives band switches via FXvtrSettings auto-flags
- TXvtrEntry: add LastCTCSSAutoActive, LastFMRptAutoActive fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:39:37 +03:00
ew8bak d08317e835 fix UpdateRptAutoState 2026-05-27 13:47:52 +03:00
ew8bakandClaude Sonnet 4.6 e6f02db399 Reorganize left panel: add RX block, move AGC, fix DUP placement and panel restore
- Add PanelRXBlock between TX and Band with CTUN, VOL, NR/NB/SNB/ANF/MUTE
- Move AGC panel to sit directly below RX block for logical RX grouping
- Move DUP button into TX panel below MOX/TUN
- Rename PanelRX → PanelAGC to match its content
- Standardize block label top (2px) and first-control top (16px) across all
  panels: AGC, SQL, CTCSS, STEP were inconsistent
- Fix left panel width restore after hide: save actual DPI-scaled width before
  zeroing instead of hardcoding 232, preventing panel truncation on Win32 with
  display scaling enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:37:47 +03:00
ew8bakandClaude Sonnet 4.6 dd9e0da29e Fix FM step snap: round to nearest instead of truncating down
(ClickFreq div StepHz)*StepHz always snapped downward; replaced with
((ClickFreq + StepHz/2) div StepHz)*StepHz so clicks above the midpoint
between two steps correctly snap to the higher one.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 19:50:55 +03:00
ew8bakandClaude Sonnet 4.6 f09c36074f Remove unused constants and clean up stale comments
- Remove TOP_VFO_BTN_H and VFO_BTN_W (unused local consts, compiler warned)
- Consolidate duplicate comment block in AudioOutput.WriteDouble
- Clean up AudioOutput callback comment header

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 19:38:20 +03:00
ew8bakandClaude Sonnet 4.6 1e7121f200 Move FreqToBandIdx to BoardUtils
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 19:26:32 +03:00
ew8bakandClaude Sonnet 4.6 9716b88b0c Extract helpers into BoardUtils, WisdomBuilder, UISync, PlatformUtils
- BoardUtils: BoardTypeName (removes duplication with DeviceForm)
- WisdomBuilder: TWisdomBuildThread + TWisdomProgressDialog
- UISync: TDeviceFoundSync/TStatusUISync/TDDCSeqSync via callbacks
  (avoids circular dependency, removes TObject casts)
- PlatformUtils: HasOpenGLSpectrumSwitch + CurrentScreenDPI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 19:15:51 +03:00
ew8bak ae1a7d9a6d Add wideband spectrum fill option 2026-05-26 17:52:02 +03:00
ew8bak e0d49c7bfa Add wideband display pane 2026-05-26 16:24:07 +03:00
ew8bak 762fad420e Guard OpenGL mode on native Wayland 2026-05-26 11:06:55 +03:00
ew8bak 08c5f0ddb7 Add OpenGL waterfall renderer 2026-05-25 20:41:06 +03:00
ew8bak 5090838563 Add OpenGL spectrum renderer 2026-05-25 20:16:37 +03:00
ew8bak 16c5f6d428 Align left panel button margins 2026-05-25 19:41:19 +03:00
ew8bakandClaude Sonnet 4.6 f87869cd91 Add FlatProgressBar — canvas-rendered cross-platform progress bar
Replaces TProgressBar in TWisdomProgressDialog with TFlatProgressBar,
drawn entirely via Canvas (plain rectangle, no native widget styling).
Dialog now receives the active TAppTheme so both dark and light themes
are applied consistently to background, label, and progress bar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 15:26:55 +03:00