diff --git a/MainForm.pas b/MainForm.pas index b2caeb8..85b595e 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -412,7 +412,6 @@ type procedure RestoreWindowBounds; procedure SaveWindowBounds; function MakeGlobalSettings: TGlobalSettings; - function MakeBandSettings: TBandSettings; procedure FreqDispBChanged(Sender: TObject; NewFreq: Int64); procedure BtnBandClick(Sender: TObject); procedure BtnModeClick(Sender: TObject); @@ -787,28 +786,6 @@ begin FController.FSettings.SaveWindowBounds(L, T, Wd, Ht, DPI, Maximized); end; -function TMainForm.MakeBandSettings: TBandSettings; -begin - Result.VfoA := FController.FVfoA; - Result.VfoB := FController.FVfoB; - Result.Mode := FController.FMode; - Result.FilterIdx := FController.FFilter; - Result.FilterBW := FController.FFilterBW; - Result.AGCMode := FController.FAGCMode; - Result.AGCTop := FController.FAGCTop; - Result.CTun := FController.FCTun; - Result.SpanHz := FController.FSpanHz; - Result.FMSQOn := FController.FFMSQOn; - Result.FMSQLevel := FController.FFMSQLevel; - Result.CTCSSOn := FController.FFMCTCSSOn; - Result.CTCSSToneIdx := FController.FFMCTCSSToneIdx; - Result.FMStepOn := FController.FFMStepOn; - Result.FMStepIdx := FController.FFMStepIdx; - Result.FMRptDir := FController.FFMRptDir; - Result.FMRptOffsetHz := FController.FFMRptOffsetHz; - // Waterfall AGC/NF — глобальные (не диапазонные) -end; - function TMainForm.MakeGlobalSettings: TGlobalSettings; var i: Integer; begin @@ -889,124 +866,21 @@ end; procedure TMainForm.SaveCurrentBand; begin - if not FController.FDevConnected then Exit; - // В XVTR-режиме НЕ трогаем HF band-cache: FController.FBandCache[FController.FCurrentBand] - // относится к HF-диапазону, на котором пользователь был ДО активации - // трансвертера, и FController.FVfoA сейчас на XVTR-частоте (144.x, 432.x и т.д.). - // Запись в FController.FBandCache[FController.FCurrentBand] стерла бы корректное HF-состояние. - // Вместо этого сохраняем LastFreq в XVTR-настройках. - if FController.FCurrentXvtr >= 0 then - begin - if (FController.FCurrentXvtr < CFG_XVTR_COUNT) then - begin - FController.FXvtrSettings.Entries[FController.FCurrentXvtr].LastFreq := FController.FVfoA; - 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; - end; - FController.FSettings.SaveXvtr(FController.FDevMAC, FController.FXvtrSettings); - Exit; - end; - // Не сохраняем, если VFO ушёл в VHF/UHF область (например, после применения - // канала на 2м без XVTR) — иначе HF-кэш диапазона будет перезаписан VHF-частотой. - if FController.FVfoA > 61000000 then Exit; - FController.FBandCache[FController.FCurrentBand] := MakeBandSettings; - FController.FSettings.SaveBand(FController.FDevMAC, FController.FCurrentBand, FController.FBandCache[FController.FCurrentBand]); + // Логика сохранения (band-cache/XVTR-слот + конфиг) — в контроллере. + FController.SaveCurrentBand; end; procedure TMainForm.RestoreBand(BandIdx: Integer); -var - B: TBandSettings; - i: Integer; begin if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit; - B := FController.FBandCache[BandIdx]; - - // --- Кнопки диапазонов --- - for i := 0 to BAND_COUNT - 1 do - StyleButton(BtnBand[i], i = BandIdx); - // Сбрасываем waterfall thresholds при смене диапазона + // Сброс порогов водопада — только при явной смене диапазона (не на каждом + // пересечении границы при тюнинге, поэтому здесь, а не в rfBand-рендере). FSpecView.ResetWfAvgBuf; - - // --- Режим: сначала устанавливаем FController.FMode, потом кнопки и WDSP --- - FController.FMode := B.Mode; - for i := 0 to MODE_COUNT - 1 do - StyleButton(BtnMode[i], i = FController.FMode); - if FController.FWDSPReady then - FController.FDSPEngine.SetMode(FController.FMode); - - // --- Фильтр --- - FController.FFilter := B.FilterIdx; - FController.FFilterBW := B.FilterBW; - UpdateFilterButtons; // обновляет кнопки фильтра - FController.ApplyModeFilter; // применяет Lo/Hi в WDSP с учётом нового FController.FMode - - // --- AGC --- - FController.FAGCMode := B.AGCMode; - FController.FAGCTop := B.AGCTop; - 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; - - // --- FM Squelch + CTCSS --- - FController.FFMSQOn := B.FMSQOn; - FController.FFMSQLevel := B.FMSQLevel; - // UI update happens inside UpdateFilterButtons (called above) when mode=FM - FController.FFMCTCSSOn := B.CTCSSOn; - if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, FController.FFMCTCSSOn); - SetFMCTCSSTone(B.CTCSSToneIdx); - ApplyFMSquelch; - FController.FFMStepOn := B.FMStepOn; - SetFMStep(B.FMStepIdx); - if BtnFMStep <> nil then StyleButton(BtnFMStep, FController.FFMStepOn); - FController.FFMRptDir := B.FMRptDir; - FController.FFMRptOffsetHz := B.FMRptOffsetHz; - if FController.FFMRptOffsetHz <= 0 then - FController.FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); - 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); - - // --- CTUN --- - FController.FCTun := B.CTun; - StyleButton(BtnCTun, FController.FCTun); - if FController.FWDSPReady and not FController.FCTun then - FController.FDSPEngine.SetShift(0.0); - - // --- Sample Rate / Span --- - // SampleRate — глобальный, не per-band. Не восстанавливаем из диапазона. - FController.FSpanHz := FController.FSampleRate; - if Assigned(FSampleRateOverlay) then - FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); - - // --- VFO A: центрируем на новой частоте, сбрасываем shift --- - FController.FCenterFreq := B.VfoA; // DDC = центр диапазона - FController.FVfoB := B.VfoB; - FreqDispB.Frequency := Round(FController.FVfoB); - - // Пересчитываем drive byte для нового диапазона перед отправкой в сеть - FController.FDriveLevel := CalcDriveByte; - // ApplyVfoA обновит сеть, FreqDisp, shift, Ruler — перерисует. - ApplyVfoA(Round(B.VfoA)); + // Состояние + DSP + события рендера — в контроллере. + FController.RestoreBand(BandIdx); + // Финальная перестройка VFO A с канальной оркестрацией (auto-CTCSS, + // pre-channel drive) — через UI-путь ApplyVfoA. + ApplyVfoA(Round(FController.FVfoA)); end; procedure TMainForm.SaveAllAndExit; @@ -1492,6 +1366,12 @@ begin if (FController.FMode = MODE_FM) and (FSpecView <> nil) then FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx]; end; + rfFMStep: + if BtnFMStep <> nil then + begin + StyleButton(BtnFMStep, FController.FFMStepOn); + BtnFMStep.Repaint; + end; rfFMSQ: begin StyleButton(BtnFMSQ, FController.FFMSQOn); diff --git a/RadioController.pas b/RadioController.pas index 39c44fd..0f6493b 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -110,6 +110,7 @@ type procedure Changed(Field: TRadioField); procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг) procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode + procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state) function ActiveVfoHz: Double; // частота активного VFO (A или B) procedure ApplyTuneCore(VfoHz: Double); // CTUN shift/center/scroll для актив. VFO public @@ -222,6 +223,15 @@ type // (band/xvtr cal, TUN/drive%, PA max). Чистая функция над состоянием. function CalcDriveByte: Byte; + // ---- Управление диапазонами (состояние + DSP; рендер через события) ---- + // MakeBandSettings — снимок текущего состояния в запись диапазона. + // SaveCurrentBand — сохраняет текущее состояние в кэш+конфиг (или в XVTR-слот). + // RestoreBand — загружает состояние диапазона, применяет к DSP, шлёт события + // рендера. Финальную перестройку VFO A делает вызывающий (UI: ApplyVfoA). + function MakeBandSettings: TBandSettings; + procedure SaveCurrentBand; + procedure RestoreBand(BandIdx: Integer); + // Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX). // Вызывается командами смены частоты/центра и UI после правки drive. procedure PushNetworkState; @@ -500,6 +510,151 @@ begin Result := Min(Round(audio_vol * 1.02 * 255.0), 255); end; +procedure TRadioController.SanitizeFilterForMode; +// Для FM индексы фильтров только 0/1 (NFM/WFM); приводим FFilter и +// синхронизируем FFilterBW/FFMDeviation. Чистая (state) часть UpdateFilterButtons. +begin + if FMode = MODE_FM then + begin + if FFilter > 1 then FFilter := FILT_FM_DEF; + FFilterBW := FILT_FM_BW[FFilter]; + FFMDeviation := FILT_FM_DEV[FFilter]; + end; +end; + +function TRadioController.MakeBandSettings: TBandSettings; +begin + Result.VfoA := FVfoA; + Result.VfoB := FVfoB; + Result.Mode := FMode; + Result.FilterIdx := FFilter; + Result.FilterBW := FFilterBW; + Result.AGCMode := FAGCMode; + Result.AGCTop := FAGCTop; + Result.CTun := FCTun; + Result.SpanHz := FSpanHz; + Result.FMSQOn := FFMSQOn; + Result.FMSQLevel := FFMSQLevel; + Result.CTCSSOn := FFMCTCSSOn; + Result.CTCSSToneIdx := FFMCTCSSToneIdx; + Result.FMStepOn := FFMStepOn; + Result.FMStepIdx := FFMStepIdx; + Result.FMRptDir := FFMRptDir; + Result.FMRptOffsetHz := FFMRptOffsetHz; + // Waterfall AGC/NF — глобальные (не диапазонные) +end; + +procedure TRadioController.SaveCurrentBand; +// Сохраняет текущее состояние диапазона. В XVTR-режиме пишем в XVTR-слот +// (HF band-cache не трогаем — там сохранён HF-диапазон до активации XVTR). +var i: Integer; +begin + if not FDevConnected then Exit; + if FCurrentXvtr >= 0 then + begin + if (FCurrentXvtr < CFG_XVTR_COUNT) then + begin + i := FCurrentXvtr; + FXvtrSettings.Entries[i].LastFreq := FVfoA; + FXvtrSettings.Entries[i].LastMode := FMode; + FXvtrSettings.Entries[i].LastFilterIdx := FFilter; + FXvtrSettings.Entries[i].LastFMSQOn := FFMSQOn; + FXvtrSettings.Entries[i].LastFMSQLevel := FFMSQLevel; + FXvtrSettings.Entries[i].LastCTCSSOn := FFMCTCSSOn; + FXvtrSettings.Entries[i].LastCTCSSToneIdx := FFMCTCSSToneIdx; + FXvtrSettings.Entries[i].LastFMStepOn := FFMStepOn; + FXvtrSettings.Entries[i].LastFMStepIdx := FFMStepIdx; + FXvtrSettings.Entries[i].LastFMRptDir := FFMRptDir; + FXvtrSettings.Entries[i].LastFMRptOffsetHz := FFMRptOffsetHz; + FXvtrSettings.Entries[i].LastCTun := FCTun; + FXvtrSettings.Entries[i].LastAGCMode := FAGCMode; + FXvtrSettings.Entries[i].LastAGCTop := FAGCTop; + FXvtrSettings.Entries[i].LastCTCSSAutoActive := FFMCTCSSAutoActive; + FXvtrSettings.Entries[i].LastFMRptAutoActive := FFMRptAutoActive; + end; + FSettings.SaveXvtr(FDevMAC, FXvtrSettings); + Exit; + end; + // Не сохраняем, если VFO ушёл в VHF/UHF (например, после канала на 2м без + // XVTR) — иначе HF-кэш диапазона перезапишется VHF-частотой. + if FVfoA > 61000000 then Exit; + FBandCache[FCurrentBand] := MakeBandSettings; + FSettings.SaveBand(FDevMAC, FCurrentBand, FBandCache[FCurrentBand]); +end; + +procedure TRadioController.RestoreBand(BandIdx: Integer); +// Загружает состояние диапазона из кэша, применяет к DSP и шлёт события рендера. +// Финальную перестройку VFO A (с канальной оркестрацией) делает вызывающий +// UI-путь через ApplyVfoA(FVfoA); здесь FVfoA лишь выставляется. +var B: TBandSettings; +begin + if (BandIdx < 0) or (BandIdx >= CFG_BAND_COUNT) then Exit; + B := FBandCache[BandIdx]; + FCurrentBand := BandIdx; + + // --- Режим --- + FMode := B.Mode; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode); + + // --- Фильтр (санитизация FM до применения к DSP — как в оригинале) --- + FFilter := B.FilterIdx; + FFilterBW := B.FilterBW; + SanitizeFilterForMode; + ApplyModeFilter; + + // --- AGC (единый маппинг — исправляет каст-баг RestoreBand) --- + FAGCMode := B.AGCMode; + FAGCTop := B.AGCTop; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetAGCTop(FAGCTop); + ApplyAGCToEngine; + + // --- FM squelch / CTCSS / step / repeater --- + FFMSQOn := B.FMSQOn; + FFMSQLevel := B.FMSQLevel; + FFMCTCSSOn := B.CTCSSOn; + FFMCTCSSToneIdx := B.CTCSSToneIdx; + if FWDSPReady and Assigned(FDSPEngine) then + begin + FDSPEngine.SetTXCTCSS(FFMCTCSSOn, CTCSS_TONES[FFMCTCSSToneIdx]); + FDSPEngine.SetFMSquelch(FFMSQOn and (FMode = MODE_FM), FFMSQLevel); + end; + FFMStepOn := B.FMStepOn; + FFMStepIdx := EnsureRange(B.FMStepIdx, 0, 3); + FFMRptDir := B.FMRptDir; + FFMRptOffsetHz := B.FMRptOffsetHz; + if FFMRptOffsetHz <= 0 then FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); + + // --- CTUN --- + FCTun := B.CTun; + if FWDSPReady and Assigned(FDSPEngine) and not FCTun then FDSPEngine.SetShift(0.0); + + // --- Span (sample rate глобальный, не per-band — лишь ресинк) --- + FSpanHz := FSampleRate; + + // --- VFO / drive --- + FCenterFreq := B.VfoA; + FVfoB := B.VfoB; + FVfoA := B.VfoA; + FDriveLevel := CalcDriveByte; + + // --- Рендер: единичные события (UI рисует через OnControllerState) --- + Changed(rfBand); + Changed(rfMode); // rfMode-рендер зовёт UpdateFilterButtons (FM-панели) + Changed(rfFilter); + Changed(rfAGCMode); + Changed(rfAGCTop); + Changed(rfFMSQ); + Changed(rfFMSQLevel); + Changed(rfFMCTCSS); + Changed(rfFMCTCSSTone); + Changed(rfFMStep); // кнопка вкл/выкл STEP + Changed(rfFMStepIdx); // дропдаун шага + web + grid + Changed(rfFMRpt); + Changed(rfCTun); + Changed(rfVfoB); + // rfVfoA НЕ шлём: финальную перестройку делает ApplyVfoA в UI-обёртке. +end; + function TRadioController.ActiveVfoHz: Double; begin if FActiveVfo = 0 then Result := FVfoA else Result := FVfoB;