diff --git a/MainForm.pas b/MainForm.pas index 2466712..9a35489 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1272,6 +1272,7 @@ end; procedure TMainForm.OnControllerState(Sender: TObject; Field: TRadioField); var i: Integer; + SavedFilter, SavedFilterBW: Integer; begin FSyncingFromController := True; try @@ -1316,8 +1317,14 @@ begin end; end; rfBand: + // В XVTR-режиме HF-кнопки гасим (активна XVTR-кнопка, см. rfXvtr). for i := 0 to BAND_COUNT - 1 do - StyleButton(BtnBand[i], i = FController.FCurrentBand); + StyleButton(BtnBand[i], + (FController.FCurrentXvtr < 0) and (i = FController.FCurrentBand)); + rfXvtr: + for i := 0 to CFG_XVTR_COUNT - 1 do + if BtnXvtrBand[i] <> nil then + StyleButton(BtnXvtrBand[i], i = FController.FCurrentXvtr); rfMute: begin StyleButton(BtnMute, FController.FMuted); @@ -1332,7 +1339,18 @@ begin rfMode: begin for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); - UpdateFilterButtons; // подписи/дефолт фильтра + FM-панели под режим + // UpdateFilterButtons (для не-FM) мутирует FFilter → сбрасывает на дефолт + // режима. Это поведение нужно при СМЕНЕ режима (web/channel), но не при + // пассивном рендере: RestoreBand/ActivateXvtr уже задали фильтр из кэша. + // На реальной смене режима контроллер выставляет дефолт через + // ApplyModeDefaults ДО rfMode, так что сохраняется именно он — поведение + // не меняется. Последующий rfFilter стилит кнопки под восстановленный фильтр. + SavedFilter := FController.FFilter; + SavedFilterBW := FController.FFilterBW; + UpdateFilterButtons; // подписи + FM-панели под режим + FController.FFilter := SavedFilter; + FController.FFilterBW := SavedFilterBW; + for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FController.FFilter); SyncSpecViewFreq; // иначе полоса фильтра рисуется под старый режим end; rfFilter: @@ -6386,24 +6404,9 @@ end; // XVTR DDC In (Alex bits 8+11) управляется через галочку XVTR в Antenna/Alex // tab для IF-диапазона (Alex.RxOnly[IF_band]=3) — аналогично Thetis. procedure TMainForm.ApplyXvtrToNetwork; -var - En: Boolean; - DisablePA: Boolean; - RxAnt: Byte; begin - if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then - begin - En := True; - DisablePA := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].DisablePA; - RxAnt := FController.FXvtrSettings.Entries[FController.FCurrentXvtr].RXAntenna; - end - else - begin - En := False; DisablePA := False; RxAnt := 0; - end; - if FController.FNetwork.Connected then - FController.FNetwork.SetXvtrMode(En, DisablePA, RxAnt); + // Логика перенесена в контроллер; обёртка для существующих вызовов в UI. + FController.ApplyXvtrToNetwork; end; // Перестраивает динамические XVTR-кнопки в PanelBands. Удаляет старые, @@ -6517,139 +6520,26 @@ end; // Активирует XVTR-band: устанавливает видимую частоту по LastFreq или // середине [FreqBegin..FreqEnd], применяет network XVTR-режим. procedure TMainForm.ActivateXvtrBand(Idx: Integer); -var - Vis: Double; - i: Integer; - E: TXvtrEntry; begin - if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit; - if not FController.FXvtrSettings.Entries[Idx].Enabled then Exit; - FController.FCurrentXvtr := Idx; - E := FController.FXvtrSettings.Entries[Idx]; - // Выбор начальной частоты: LastFreq если в диапазоне, иначе середина - Vis := E.LastFreq; - if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then - Vis := (E.FreqBegin + E.FreqEnd) / 2.0; - // Отключаем кнопки HF band - for i := 0 to BAND_COUNT - 1 do - StyleButton(BtnBand[i], False); - // Подсвечиваем активную XVTR-кнопку - for i := 0 to CFG_XVTR_COUNT - 1 do - if BtnXvtrBand[i] <> nil then - StyleButton(BtnXvtrBand[i], i = Idx); - // Применяем XVTR режим к сети (XVTR enable bit + DDC IN + DisablePA + RX ant) - ApplyXvtrToNetwork; - // Восстанавливаем Mode + FM Squelch + CTCSS для этого XVTR-слота - FController.FFMSQOn := E.LastFMSQOn; - FController.FFMSQLevel := E.LastFMSQLevel; - FController.FMode := E.LastMode; - FController.FFilter := E.LastFilterIdx; - for i := 0 to MODE_COUNT - 1 do StyleButton(BtnMode[i], i = FController.FMode); - if FController.FWDSPReady then FController.FDSPEngine.SetMode(FController.FMode); - UpdateFilterButtons; // показывает/скрывает FM-панели, обновляет SQL UI - FController.ApplyModeFilter; - FController.FFMCTCSSOn := E.LastCTCSSOn; - FController.FFMCTCSSAutoActive := E.LastCTCSSAutoActive; - if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); - SetFMCTCSSTone(E.LastCTCSSToneIdx); - ApplyFMSquelch; - FController.FFMStepOn := E.LastFMStepOn; - SetFMStep(E.LastFMStepIdx); - if BtnFMStep <> nil then StyleButton(BtnFMStep, FController.FFMStepOn); - FController.FFMRptDir := E.LastFMRptDir; - FController.FFMRptAutoActive := E.LastFMRptAutoActive; - FController.FFMRptOffsetHz := E.LastFMRptOffsetHz; - if FController.FFMRptOffsetHz <= 0 then - FController.FFMRptOffsetHz := RptDefaultOffsetHz(E.FreqBegin); - if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, FController.FFMRptDir = RPT_MINUS); - if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, FController.FFMRptDir = RPT_PLUS); - if EdFMRptOffset <> nil then EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz); - // AGC - FController.FAGCMode := E.LastAGCMode; - FController.FAGCTop := E.LastAGCTop; - for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); - TrkAGC.Position := FController.FAGCTop; - LblAGCTop.Caption := Format('%ddB', [FController.FAGCTop]); - if FController.FWDSPReady then - begin - FController.FDSPEngine.SetAGCTop(FController.FAGCTop); - FController.FDSPEngine.SetAGC(TWDSPAGCMode(FController.FAGCMode), 50.0); - end; - FSpecView.AGCTop := FController.FAGCTop; - // CTUN - FController.FCTun := E.LastCTun; - StyleButton(BtnCTun, FController.FCTun); - // VFO-B: своя сохранённая частота слота, клэмп в диапазон трансвертера. - FController.FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd); - FreqDispB.Frequency := Round(FController.FVfoB); - // VFO на видимую частоту; FController.FCenterFreq тоже visible - FController.FVfoA := Vis; - FController.FCenterFreq := Vis; - FreqDispA.Frequency := Round(FController.FVfoA); - if FController.FWDSPReady then FController.FDSPEngine.SetShift(0.0); - // Пересчитываем drive byte для нового XVTR слота и отправляем в сеть - FController.FDriveLevel := CalcDriveByte; - if FController.FNetwork.Connected and FController.FNetwork.Running then - begin - FController.FNetwork.UpdateState(XvtrTranslate(FController.FCenterFreq), XvtrTranslate(ActiveTXFreqHz), - FController.FDriveLevel, FController.FTransmitting, True, True); - FController.FNetwork.SendFullHP; - end; - SyncSpecViewFreq; - FSpecView.DrawSpectrum; - PbSpectrum.Invalidate; - FSpecView.DrawWaterfall; - PbWaterfall.Invalidate; - if PbRuler <> nil then PbRuler.Invalidate; + // Вход в XVTR-слот (state+DSP+VFO+сеть) — в контроллере; рендер кнопок/частот/ + // спектра — через события (rfXvtr/rfBand/rfActiveVfo). Здесь только UI: wideband+web. + FController.ActivateXvtr(Idx); UpdateWidebandFrequencyView; if PbWideband <> nil then PbWideband.Invalidate; if PbWidebandRuler <> nil then PbWidebandRuler.Invalidate; PushXvtrToWeb; end; -// Деактивирует XVTR — возвращает к HF-режиму. -// FController.FCurrentBand был сохранён неизменным во время XVTR (см. ActivateXvtrBand, -// ApplyVfoA, SaveCurrentBand) — он указывает на HF-диапазон, с которого -// пользователь зашёл в трансвертер. RestoreBand вернёт VFO/Mode/Filter/AGC -// /CTUN/Span этого диапазона и обновит подсветку HF band-кнопки. +// Деактивирует XVTR — возвращает к HF-режиму. FController.FCurrentBand указывает +// на HF-диапазон, с которого пользователь зашёл в трансвертер. RestoreBand вернёт +// VFO/Mode/Filter/AGC/CTUN/Span этого диапазона и обновит подсветку HF band-кнопки. procedure TMainForm.DeactivateXvtr; -var i: Integer; begin if FController.FCurrentXvtr < 0 then Exit; - // Сохраняем полное состояние XVTR-слота (раньше сохранялся только LastFreq) - if FController.FCurrentXvtr < CFG_XVTR_COUNT then - begin - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreqB := FController.FVfoB; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastMode := FController.FMode; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFilterIdx := FController.FFilter; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQOn := FController.FFMSQOn; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMSQLevel := FController.FFMSQLevel; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSOn := FController.FFMCTCSSOn; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSToneIdx := FController.FFMCTCSSToneIdx; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepOn := FController.FFMStepOn; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMStepIdx := FController.FFMStepIdx; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptDir := FController.FFMRptDir; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptOffsetHz := FController.FFMRptOffsetHz; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTun := FController.FCTun; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCMode := FController.FAGCMode; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastAGCTop := FController.FAGCTop; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastCTCSSAutoActive := FController.FFMCTCSSAutoActive; - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFMRptAutoActive := FController.FFMRptAutoActive; - if FController.FDevConnected then - begin - FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); - FController.FSettings.Save; - end; - end; - FController.FCurrentXvtr := -1; - // Сбрасываем подсветку XVTR-кнопок - for i := 0 to CFG_XVTR_COUNT - 1 do - if BtnXvtrBand[i] <> nil then - StyleButton(BtnXvtrBand[i], False); - ApplyXvtrToNetwork; - // Восстанавливаем HF band: VFO/Mode/Filter/AGC/CTUN/Span из кэша. - // RestoreBand сам обновит подсветку HF band-кнопки и сетевое состояние. + // Сохранение слота + выход (+ гашение XVTR-кнопок через rfXvtr) — в контроллере. + FController.DeactivateXvtr; + // HF-возврат: состояние/DSP из кэша + финальная перестройка активного VFO + // (через UI-путь RestoreBand: ResetWfAvgBuf + controller.RestoreBand + ApplyVfoA/SetVfoB). if (FController.FCurrentBand >= 0) and (FController.FCurrentBand < CFG_BAND_COUNT) then RestoreBand(FController.FCurrentBand); UpdateWidebandFrequencyView; diff --git a/RadioController.pas b/RadioController.pas index 042bcbe..095fb90 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -235,6 +235,18 @@ type // рендера. НЕ трогает частоты VFO/центр — это переиспользуется как RestoreBand // (с загрузкой VFO), так и SetActiveVfo (bandstack: смена бэнда без сброса VFO). procedure ApplyBandDSP(const B: TBandSettings); + // Полоса фильтра для (режим, индекс фильтра) — выбор per-mode таблицы. + function FilterBWFor(M, Idx: Integer): Integer; + + // ---- XVTR (трансвертер) ---- + // ApplyXvtrToNetwork — XVTR enable bit + DisablePA + RX ant в сеть. + // ActivateXvtr — вход в слот: state+DSP (через ApplyBandDSP) + VFO/центр + сеть; + // рендер через rfXvtr/rfBand(XVTR-aware)/rfActiveVfo. UI: wideband+web. + // DeactivateXvtr — сохраняет слот (SaveCurrentBand) и выходит; HF-возврат + // (RestoreBand + финальная перестройка VFO) делает UI-обёртка. + procedure ApplyXvtrToNetwork; + procedure ActivateXvtr(Idx: Integer); + procedure DeactivateXvtr; // Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX). // Вызывается командами смены частоты/центра и UI после правки drive. @@ -463,6 +475,19 @@ begin end; end; +function TRadioController.FilterBWFor(M, Idx: Integer): Integer; +// Полоса выбранного фильтра под режим. Зеркалит выбор таблицы в UpdateFilterButtons, +// но НЕ меняет индекс (в отличие от ApplyModeDefaults) — нужно для XVTR-restore. +begin + case M of + MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, 1)]; + 0, 1: Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)]; + 2: Result := FILT_DSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)]; + 3, 4: Result := FILT_CW_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)]; + else Result := FILT_AM_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)]; + end; +end; + // ===================================================================== // Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm. // ===================================================================== @@ -1018,8 +1043,102 @@ begin if FCurrentBand < CFG_BAND_COUNT - 1 then SetBand(FCurrentBand + 1); end; procedure TRadioController.BandDown; begin if FCurrentBand > 0 then SetBand(FCurrentBand - 1); end; +procedure TRadioController.ApplyXvtrToNetwork; +// Передаёт XVTR-режим в сеть (enable bit + T/R relay suppression + RX ant). +var + En, DisablePA: Boolean; + RxAnt: Byte; +begin + if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and + FXvtrSettings.Entries[FCurrentXvtr].Enabled then + begin + En := True; + DisablePA := FXvtrSettings.Entries[FCurrentXvtr].DisablePA; + RxAnt := FXvtrSettings.Entries[FCurrentXvtr].RXAntenna; + end + else + begin + En := False; DisablePA := False; RxAnt := 0; + end; + if Assigned(FNetwork) and FNetwork.Connected then + FNetwork.SetXvtrMode(En, DisablePA, RxAnt); +end; + +procedure TRadioController.ActivateXvtr(Idx: Integer); +// Вход в XVTR-слот: state+DSP через общий ApplyBandDSP + VFO/центр/сеть. +// Рендер: rfXvtr (XVTR-кнопки), rfBand (XVTR-aware гасит HF), rfActiveVfo +// (дисплеи A/B + полный перерисов спектра). UI-обёртка добавляет wideband+web. +var + E: TXvtrEntry; + B: TBandSettings; + Vis: Double; +begin + if (Idx < 0) or (Idx >= CFG_XVTR_COUNT) then Exit; + if not FXvtrSettings.Entries[Idx].Enabled then Exit; + FCurrentXvtr := Idx; + E := FXvtrSettings.Entries[Idx]; + // Начальная частота: LastFreq если в диапазоне, иначе середина. + Vis := E.LastFreq; + if (Vis < E.FreqBegin) or (Vis > E.FreqEnd) then + Vis := (E.FreqBegin + E.FreqEnd) / 2.0; + + ApplyXvtrToNetwork; // XVTR enable bit + DisablePA + RX ant + + // Авто-флаги канала (не входят в TBandSettings/ApplyBandDSP). + FFMCTCSSAutoActive := E.LastCTCSSAutoActive; + FFMRptAutoActive := E.LastFMRptAutoActive; + + // DSP-настройки слота — через общий путь Band (mode/filter/AGC/CTUN/FM + рендер). + B.Mode := E.LastMode; + B.FilterIdx := E.LastFilterIdx; + B.FilterBW := FilterBWFor(E.LastMode, E.LastFilterIdx); + B.AGCMode := E.LastAGCMode; + B.AGCTop := E.LastAGCTop; + B.CTun := E.LastCTun; + B.SpanHz := FSampleRate; + B.FMSQOn := E.LastFMSQOn; + B.FMSQLevel := E.LastFMSQLevel; + B.CTCSSOn := E.LastCTCSSOn; + B.CTCSSToneIdx := E.LastCTCSSToneIdx; + B.FMStepOn := E.LastFMStepOn; + B.FMStepIdx := E.LastFMStepIdx; + B.FMRptDir := E.LastFMRptDir; + B.FMRptOffsetHz := E.LastFMRptOffsetHz; + B.VfoA := Vis; + B.VfoB := FVfoB; + ApplyBandDSP(B); + + // VFO: B — своя сохранённая частота слота (клэмп в диапазон); A = visible. + FVfoB := EnsureRange(E.LastFreqB, E.FreqBegin, E.FreqEnd); + FVfoA := Vis; + FCenterFreq := Vis; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0); + FDriveLevel := CalcDriveByte; + PushNetworkState; + + Changed(rfXvtr); + Changed(rfActiveVfo); +end; + +procedure TRadioController.DeactivateXvtr; +// Сохраняет текущий XVTR-слот и выходит в HF. Сам HF-возврат (RestoreBand + +// финальная перестройка активного VFO) делает UI-обёртка — там же ResetWfAvgBuf. +begin + if FCurrentXvtr < 0 then Exit; + SaveCurrentBand; // XVTR-ветка: пишет весь слот + SaveXvtr + if FDevConnected then FSettings.Save; + FCurrentXvtr := -1; + ApplyXvtrToNetwork; // снять XVTR enable bit + Changed(rfXvtr); // погасить XVTR-кнопки +end; + procedure TRadioController.SetXvtrBand(Idx: Integer); -begin FCurrentXvtr := Idx; Changed(rfXvtr); { TODO wiring } end; +// Единая точка для GUI/Web/CAT: Idx>=0 — войти в слот, Idx<0 — выйти в HF. +// ВНИМАНИЕ: HF-возврат при выходе делает UI-обёртка DeactivateXvtr (RestoreBand). +begin + if Idx >= 0 then ActivateXvtr(Idx) + else DeactivateXvtr; +end; procedure TRadioController.SetSpan(Hz: Integer); begin FSpanHz := Hz; Changed(rfSpan); { TODO wiring } end;