Commit Graph
269 Commits
Author SHA1 Message Date
Uladzimir KarpenkaandClaude Opus 4.8 2f8b0bdf48 Phase 3 (WA1): extract web adapter into WebAdapter.pas
Move the pure controller-command web handlers out of MainForm into a
new TWebAdapter (bridge between TWebServer and TRadioController),
marshalling via FController.Invoke so it is daemon-ready.

- 20 handlers moved: Mode/Filter/AGC/AGCTop/Volume/WfAGC/WfNF/Mute/
  NR/NB/SNB/ANF/FreqB/Center/Attn/ActiveVfo/Ctun/MOX/Tun/FMStep +
  ClientActiveChanged. Each calls a controller command directly.
- CAT context (Ctx.Set*) and CATSetFilterIdx repointed to the adapter,
  since CAT reuses these as its command layer.
- Host-dependent handlers (Freq/Band/XvtrBand/Span/Run/Drive/connect/
  disconnect/CRUD/mic) and the outgoing PushSpectrum mirror stay in
  MainForm for now (WA2/WA3); IWebHost is the placeholder seam.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 16:03:49 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 c4981fcb19 Phase 3 (batch 33+34): web device discovery/connect parity
Move discovery into the controller and add a full web device overlay
(discovery + connect + saved-device CRUD), reaching parity with the
desktop device dialog.

- RadioController: OnDeviceFound/Discover/DiscoverFinishedNone +
  rfDeviceList; TDiscoverThread moved in from MainForm.
- WebServer: device protocol (discover/connect/disconnect/dev_add/
  dev_remove/dev_autostart) + "devices" in BuildStateJson.
- MainForm: web handlers (marshalled), PushDeviceListToWeb, extracted
  DoStopAndDisconnect, detached WebOnRun from BtnStartStopClick;
  web connect resolves board type from discovered then saved by IP.
- DeviceForm: RefreshFound renders the found list from the store.
- WebPageHtml: DEV overlay; START opens it, STOP disconnects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 15:30:03 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 9dec47a6ca Phase 3 (batch 32): extract StartWithDevice from BtnStartStopClick
Factor the "start with an already-chosen device" sequence (preload sample
rate + async WDSP open + DoConnectDevice) out of BtnStartStopClick into
StartWithDevice(Dev). BtnStartStopClick's START branch now resolves the
device (dialog/autostart) and calls StartWithDevice; the web overlay will
reuse the same method to connect without the modal dialog. Behavior-preserving.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:49:09 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 7f7f429515 Phase 3 (batch 31a): shared TDeviceStore for saved+discovered devices
Introduce DeviceStore.pas (TDeviceStore) as the single source of truth for
saved devices (hpsdr_devices.ini CRUD + autostart) and the current discovery
list. The controller owns one instance (created/freed in its ctor/dtor);
desktop and (later) web frontends edit/render through it so the lists never
diverge.

Refactor TDeviceDialog to use a TDeviceStore reference instead of its own
arrays + ini code. MainForm wires FDeviceDialog.Store to the controller's
store, routes discovery results (DoAddDevice) and the preload-rate lookup
through it (TDiscoveredDevice now carries the MAC), and drops the now-unused
FDevices/TDeviceItem. Behavior-preserving for the desktop dialog.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:36:43 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 783c8976c9 Phase 5 (batch 30): move device connect/load-by-MAC into the controller
Fill controller.ConnectDevice with the DoConnectDevice load core: Connect,
load per-device settings by MAC (TX/Alex/XVTR, PA cal, drive%, sample rate,
NR/NB/SNB/ANF, Wf/Show/Spec params) into controller fields, configure DSP
display + open audio devices. Returns False on connect failure (UI shows the
message). New FLoadedGlobal field retains the loaded TGlobalSettings so the UI
can render fields the controller doesn't own (FPS/FreqMhzDigits/CAT).

New OnControllerState(rfDevice) renders all device-level widgets from state
(drive slider, NR/NB buttons, FSpecView/Wideband params, DUP, sample-rate
overlay, CAT apply, web push, FPS, freq digits). drive% now loads directly
into FDrivePercent instead of via TrkDrive.Position; the slider reflects it in
the render. DoConnectDevice collapses to a thin wrapper: ConnectDevice ->
RestoreBand/XVTR/active-VFO -> StartRunning.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 12:10:26 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 04f621bf3d Phase 5 (batch 29): move Run=1 go-live (StartRunning) into the controller
Move the device bring-up tail of DoConnectDevice into TRadioController,
symmetric with StopRunning:

- Relocate four pure DSP/network helpers into the controller
  (BuildMicLineSelectByte, ApplyNoiseFilterButtonsToDSP,
  SendDUCSpecificFromSettings, ApplyTXSettingsToDSP); external callers
  (OnTXSettingsChange) now call FController.*.
- New StartRunning(SpectrumWidth): General packet, wideband/DDC config,
  WDSP sample-rate sync, DSP restore (mode/volume/filter/NR/AGC), Run=1
  sequence, seq reset, DDC/DUC Specific + TX chain, AGC lines. Emits
  Changed(rfRunning) so the run button / MOX enable / sleep inhibit render
  through OnControllerState.

DoConnectDevice's ~80-line Phase C collapses to one call; only UI bits
(FSpecView.AGCTop, status text, ResizeSpectrumPanels) stay in the wrapper.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:31:04 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 1fdbe27b96 Phase 5 (batch 28): move display state into the controller
Move the 14 persisted display-state fields (FShow*/FSpec*/FWf*/FTXSpec*/
FWidebandFill/FSpectrumFill) from MainForm into TRadioController so a
headless device load (DoConnectDevice) can apply them without the UI. Pure
field relocation: every reader/writer now addresses FController.F*, render
still happens in MainForm against FSpecView/FWidebandView. Compiler-verified
complete; behavior preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 11:15:58 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 9fdfc67f65 Phase 5 (batch 27): move Run=0 teardown (StopRunning) into the controller
Extract the graceful Run=0 teardown from BtnStartStopClick's STOP branch
into TRadioController.StopRunning: UpdateState/SetRunAndFreq(False) at zero
drive, clear FRunning/FTransmitting, SetTXRun(False), reset RX telemetry,
emit Changed(rfTransmitting) + rfRunning. The network stays Connected so the
UI wrapper can persist state before Disconnect.

New OnControllerState(rfRunning) render handles the START/STOP button,
BtnMOX.Enabled and sleep inhibit; the context-specific offline status text
stays with the caller. FormClose reuses StopRunning (same teardown).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 10:59:14 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 452795dc59 Fix: rewire OnPullMicSamples in RecreateDSPEngine
RecreateDSPEngine rebound OnAudio/OnSpectrum/OnWaterfall/OnTXIQ onto the
freshly created DSP engine but not OnPullMicSamples, so the sound-card mic
path was lost after a sample-rate change. Rebind it alongside the others.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 10:50:24 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 3a387e06ce Phase 5 (batch 26): move OnTXIQ + DUC queue into the controller
The TX IQ handler (batch WDSP TXA output into 240-sample DUC packets,
24-bit clamp, FNetwork.SendDUCIQ) moves into TRadioController.OnTXIQ,
along with the FDUCPendingI/Q/Count queue fields. The DUC-queue reset on
a TX-state change moves from the rfTransmitting render into SetMOX itself
(the render does not run headless). Both DSP-callback wiring sites
(FormCreate and RecreateDSPEngine) now point at FController.OnTXIQ.
Backend-agnostic: a Pluto backend converts 24-bit->int16 in SendDUCIQ.

Pre-existing gap noted: RecreateDSPEngine never re-wires OnPullMicSamples,
so sound-card mic is lost after a sample-rate change (unchanged here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 23:42:22 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 c1da506f73 Phase 5 (batch 25b): move HP-status callback into the controller
OnHPStatusCB + DoUpdateStatus move into TRadioController as OnHPStatus
(network thread: board-specific supply/power/SWR decode -> pending fields
-> Invoke) and ApplyHPStatus (marshalled: PEP-style fwd/SWR ballistics +
supply EMA into FLast*, ADC-overload via Changed(rfADCOverload), and the
HW-PTT edge -> SetMOX). FNetwork.OnHPStatus is wired to the controller;
the ADC-overload indicator renders via OnControllerState(rfADCOverload).
The HWPTT->TX path is now headless-capable and reads an always-current
FWebClientActive for mic-source. TStatusUISync is left dead in UISync.pas
(unit still used for TDDCSeqSync) pending a separate cleanup.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 23:35:35 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 9d2324cc6c Phase 5 (batch 25a): de-dup telemetry fields + push web-client-active to core
Prep for moving the HP-status callback into the controller:
- Remove the duplicate FLast{SMeter,FwdW,SWR,SupplyV,SupplyA,PLLLock}
  fields from MainForm; the controller (which already declared them) is
  now the sole owner. The UI-only S-meter ballistics (FSMeterPeak/Min/Avg)
  stay in MainForm.
- TWebServer gains an OnClientActiveChanged event (fired via a
  SetClientActive setter at the three client connect/disconnect points);
  MainForm mirrors it into FController.FWebClientActive. The lazy mirror
  in ApplyMOX/ApplyTUN is removed, so the flag is always current — needed
  for the HWPTT->SetMOX path that moves into the controller next.

Behaviour-preserving (the flag held the same value at MOX time before).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:55:13 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 239ff54942 Phase 5 (batch 24): move OnDDCIQ + seq telemetry into the controller
The DDC IQ receive handler (sequence-error tracking + feeding the active
DDC into WDSP) moves verbatim into TRadioController.OnDDCIQ, along with
the 12 RX/seq telemetry fields it owns (FRXPacketCount, FRXStartTime,
FRXLastPktTime, FActiveDDC, FLastDDCSeq, FLastDDCIndex, FDDCLastSeq,
FDDCSeqValid, FSeqErrorCount, FLastSeqErrorDDC, FLastSeqErrorDelta,
FSeqOkStreak). FNetwork.OnDDCIQ is wired to the controller method; all
MainForm readers (meter timer RX-running/stall detection, status-bar SEQ
text, web seq text) and the START/STOP/connect resets now go through
FController. Behaviour unchanged; backend-agnostic (a future Pluto
backend synthesises the same TDDCIQPacket).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:39:22 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 3d2d76452c Phase 5 (batch 23): move TX mic callbacks into the controller
OnMicPacket (HW mic packet -> WDSP, only when TX mic source is Radio) and
PullSoundCardMic (sound-card input -> WDSP, when source is SoundCard) are
pure data-path handlers with no UI/MainForm state, so they move verbatim
into TRadioController. FormCreate now wires FNetwork.OnMicPacket and
FDSPEngine.OnPullMicSamples to the controller's own methods. These are
backend-agnostic consumers (they operate on the common interchange
format), so they belong in the core regardless of HPSDR vs future Pluto
backends. Behaviour unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:33:11 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 8be98b72e1 Phase 5 (batch 22): move engine lifecycle into the controller
TRadioController now owns its engines: CreateEngines(ASampleRate) builds
FNetwork/FDSPEngine/FAudioOut/FAudioIn (and the constructor now owns
FSettings), FreeEngines closes and frees them, and Destroy calls
FreeEngines + frees FSettings. FormCreate replaces the four inline
T...Create calls with a single FController.CreateEngines (after the
startup sample rate is loaded, so FDSPEngine is still born at the right
rate) and keeps wiring the MainForm callbacks. FormDestroy keeps the
graceful Run=0 stop + Disconnect but drops the per-engine Close/Free and
FSettings.Free, which now happen in the controller's destructor. This is
a behaviour-preserving ownership move toward headless/daemon operation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:20:17 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 33c433ce5e Phase 3 (batch 21): route web Mode/Filter through controller commands
SyncWebMode now delegates to FController.SetMode (default filter + DSP +
band cache; render via rfMode), and SyncWebFilter to FController.SetFilterBW,
whose stub is filled in: it applies the requested bandwidth and highlights
the nearest preset (render via rfFilter). The JS client only ever sends a
bandwidth in Hz ({cmd:"filter",bw:N}), so the old negative-index branch
was dead code and is removed. Both web handlers now use the same controller
commands as the desktop; the duplicated inline mode/filter logic is gone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:06:18 +03:00
Uladzimir KarpenkaandClaude Opus 4.8 c7d93c2e46 Phase 3 (batch 20): unify web/CAT VFO-B onto the SetVfoB command
SyncWebFreq (VFO-B branch), SyncWebFreqB and SyncCATVfoB each carried a
copy of the pre-SetVfoB inline VFO-B block that pushed the radio via
SetRunAndFreq(True,...). That reset the HP sequence counter on every
freq change (only correct at Run start), left FIsTransmitting/Alex flags
stale, skipped the drive recalc on a band crossing, and forced shift=0
(ignoring CTUN). All three now delegate to FController.SetVfoB, so web,
CAT and desktop share one path (ApplyTuneCore + PushNetworkState) with
rendering via OnControllerState(rfVfoB/rfBand). ~70 lines removed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 16:55:04 +03:00
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 8f0ba64d67 Fix CTUN not working on VFO B
Two A-hardcoded spots broke CTUN whenever VFO B was the active receiver:
- SetVfoB forced shift=0 (no CTUN handling), so click/wheel/edit on B
  re-centered the DDC instead of shifting the demodulator.
- SetCTun, on disable, re-centered on FVfoA regardless of active VFO.

Extract the CTUN shift/center/edge-scroll logic into a shared private
ApplyTuneCore(VfoHz) used by both SetVfoA and SetVfoB, and add ActiveVfoHz.
SetCTun now centers on ActiveVfoHz. CTUN behaves identically on A and B.

This also was a latent gap pre-refactor (the old inline VFO B tune blocks
never honored CTUN). Builds clean, links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 20:35:25 +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 a85f196c97 Phase 3 (slice): make Mute sync UI->web in the web client
The outbound channel already existed: PushLoop broadcasts the full
BuildStateJson (including "mute") to web clients every 200ms. The gap was
purely client-side — applyState never rendered the mute icon, so a
UI-initiated mute change was not reflected in the browser (the icon was
only updated on the client's own click).

WebPageHtml.pas:
- Add updMuteIc() helper; render it from applyState (guarded by
  isPending("mute")), so periodic state updates the icon.
- Click handler calls updMuteIc + pend("mute"); add "mute" to PEND_MAP so
  the optimistic local state isn't clobbered by an in-flight stale push.

Mute is now bidirectional. Other fields already render in applyState, so
UI->web works for them; this confirms the web outbound path is the
periodic full-state broadcast, not a per-field event push (the latter
will matter for the future serial/Arduino frontend).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 11:16:26 +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
ew8bak 261b3190b8 BUILD.MD and inc numBuild 2026-06-02 18:08:10 +03:00
ew8bak 1e9796bc9a Enable build number auto-increment 2026-06-02 18:01:46 +03:00