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>
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>
- 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>
Первая команда тяжёлого класса. 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>
Перенос частотных хелперов (общий блокер для 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>