From 443da44e400acb006867d1afea9f541bb4683dad Mon Sep 17 00:00:00 2001 From: Uladzimir Karpenka Date: Wed, 11 Mar 2026 21:43:37 +0300 Subject: [PATCH] vix vfo-a/vfo-b --- MainForm.pas | 344 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 310 insertions(+), 34 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index c0c0d8b..5707e6a 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -410,6 +410,10 @@ type procedure BtnStartStopClick(Sender: TObject); procedure FreqDispAChanged(Sender: TObject; NewFreq: Int64); procedure ApplyVfoA(NewFreq: Int64); + procedure FreqDispAClick(Sender: TObject); + procedure FreqDispBClick(Sender: TObject); + procedure ActivateVfo(Idx: Integer); + function ActiveVfoFreq: Int64; // Настройки procedure SaveCurrentBand; procedure RestoreBand(BandIdx: Integer); @@ -1068,6 +1072,7 @@ begin FreqDispA.ColorHover := TColor($0040DDFF); FreqDispA.ColorDim := CLR_TEXTDIM; FreqDispA.OnChange := FreqDispAChanged; + FreqDispA.OnClick := FreqDispAClick; Inc(Y, 58); @@ -1094,6 +1099,7 @@ begin FreqDispB.ColorHover := TColor($0040DDFF); FreqDispB.ColorDim := CLR_TEXTDIM; FreqDispB.OnChange := FreqDispBChanged; + FreqDispB.OnClick := FreqDispBClick; Inc(Y, 50); @@ -1727,27 +1733,26 @@ begin FreqDispB.Frequency := Round(FVfoB); if FActiveVfo = 0 then begin + // VFO-A активен: яркий зелёный, крупный FreqDispA.ColorNormal := CLR_FREQ; FreqDispA.FontSize := 20; FreqDispB.ColorNormal := CLR_FREQ_DIM; FreqDispB.FontSize := 14; FreqDispA.Height := 36; FreqDispB.Height := 28; + LblVfoALabel.Font.Color := CLR_FREQ; + LblVfoBLabel.Font.Color := CLR_TEXTDIM; end else begin + // VFO-B активен: яркий зелёный, крупный FreqDispA.ColorNormal := CLR_FREQ_DIM; FreqDispA.FontSize := 14; FreqDispB.ColorNormal := CLR_FREQ; FreqDispB.FontSize := 20; FreqDispA.Height := 28; FreqDispB.Height := 36; + LblVfoALabel.Font.Color := CLR_TEXTDIM; + LblVfoBLabel.Font.Color := CLR_FREQ; end; - // FCenterFreq обновляется через ApplyVfoA, здесь не трогаем - - // При изменении частоты — обновляем HP пакет с новыми частотами и ALEX - if FNetwork.Connected and FNetwork.Running then - begin - FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, - FTransmitting, True, True); - FNetwork.SendFullHP; - end; - - // WDSP: частота NCO задаётся через HPSDR железо (DDC), не через WDSP напрямую + FreqDispA.Invalidate; + FreqDispB.Invalidate; + LblVfoALabel.Invalidate; + LblVfoBLabel.Invalidate; end; // =========================================================================== @@ -2063,7 +2068,11 @@ begin // ── 2. Полоса фильтра — заливка ДО спектра ───────────────────────────── Half := FFilterBW / 2; - VfoX := Round((FVfoA - FCenterFreq + FSpanHz / 2) / FSpanHz * W); + // Используем частоту активного VFO (A или B) для позиции маркера и фильтра + if FActiveVfo = 0 then + VfoX := Round((FVfoA - FCenterFreq + FSpanHz / 2) / FSpanHz * W) + else + VfoX := Round((FVfoB - FCenterFreq + FSpanHz / 2) / FSpanHz * W); case FMode of 0: begin Lo_Hz := -FFilterBW; Hi_Hz := -100; end; 1: begin Lo_Hz := 100; Hi_Hz := FFilterBW; end; @@ -2652,11 +2661,11 @@ begin H := PbRuler.Height; if (W <= 0) or (H <= 0) then Exit; - // Пропускаем перерисовку если ни частота центра, ни VFO не изменились + // Пропускаем перерисовку если ни частота центра, ни активный VFO не изменились if (Abs(FCenterFreq - FRulerLastFreq) < 0.5) and - (Abs(FVfoA - FRulerLastVfo) < 0.5) then Exit; + (Abs(ActiveVfoFreq - FRulerLastVfo) < 0.5) then Exit; FRulerLastFreq := FCenterFreq; - FRulerLastVfo := FVfoA; + FRulerLastVfo := ActiveVfoFreq; if (FRulerBitmap.Width <> W) or (FRulerBitmap.Height <> H) then FRulerBitmap.SetSize(W, H); @@ -2682,7 +2691,7 @@ begin C.Font.Color := TColor($00C8D8E2); C.TextOut(X - TW div 2, H div 2 - 1, Lbl); end; - VfoX := Round((FVfoA - FCenterFreq + FSpanHz / 2) / FSpanHz * W); + VfoX := Round((ActiveVfoFreq - FCenterFreq + FSpanHz / 2) / FSpanHz * W); if (VfoX >= 0) and (VfoX < W) then begin C.Pen.Color := TColor($0000FF80); @@ -3381,23 +3390,178 @@ begin end; +// --------------------------------------------------------------------------- +// ActivateVfo — переключаем активный VFO и перестраиваем приёмник. +// Idx=0 → VFO-A активен, Idx=1 → VFO-B активен. +// Перестройка всегда происходит на частоту активного VFO (не на оффсет). +// CTUN при этом сбрасывается чтобы не было путаницы с оффсетом. +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// FreqToBandIdx — возвращает индекс диапазона для частоты Hz, или -1 +// --------------------------------------------------------------------------- +function FreqToBandIdx(Hz: Double): Integer; +const + // Границы диапазонов [Low, High] в Гц (приблизительные) + BAND_LO: array[0..10] of Double = ( + 1800000, 3500000, 5330000, 7000000, 10100000, + 14000000, 18068000, 21000000, 24890000, 28000000, 50000000); + BAND_HI: array[0..10] of Double = ( + 2000000, 4000000, 5410000, 7300000, 10150000, + 14350000, 18168000, 21450000, 24990000, 29700000, 54000000); +var + i: Integer; +begin + Result := -1; + for i := 0 to 10 do + if (Hz >= BAND_LO[i]) and (Hz <= BAND_HI[i]) then + begin + Result := i; + Exit; + end; +end; + +// --------------------------------------------------------------------------- +// ActiveVfoFreq — частота активного VFO +// --------------------------------------------------------------------------- +function TMainForm.ActiveVfoFreq: Int64; +begin + if FActiveVfo = 0 then Result := Round(FVfoA) + else Result := Round(FVfoB); +end; + +// --------------------------------------------------------------------------- +// ActivateVfo — переключаем активный VFO и перестраиваем приёмник. +// Idx=0 → VFO-A активен, Idx=1 → VFO-B активен. +// Перестройка на частоту выбранного VFO. CTUN не трогаем. +// --------------------------------------------------------------------------- +procedure TMainForm.ActivateVfo(Idx: Integer); +var + ActiveFreq: Int64; + NewBand: Integer; + Offset: Double; + HalfSpan: Double; + FreqVisible: Boolean; +begin + FActiveVfo := Idx; + + // Частота нового активного VFO + if FActiveVfo = 0 then + ActiveFreq := Round(FVfoA) + else + ActiveFreq := Round(FVfoB); + + // Обновляем визуальный дисплей + UpdateVfoDisplay; + + if FCTun then + begin + // CTUN включён: проверяем, видна ли новая частота на текущем спектре + HalfSpan := FSpanHz / 2; + Offset := ActiveFreq - FCenterFreq; + FreqVisible := (Offset > -HalfSpan) and (Offset < HalfSpan); + + if FreqVisible then + begin + // Частота видна в окне — просто меняем shift, DDC не трогаем + if FWDSPReady then FDSPEngine.SetShift(Offset); + // Сеть не трогаем — DDC (FCenterFreq) остаётся прежним + end + else + begin + // Частота за пределами спектра — центрируем окно и сбрасываем CTUN + FCTun := False; + StyleButton(BtnCTun, False); + FBandCache[FCurrentBand].CTun := False; + FCenterFreq := ActiveFreq; + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + end; + end + else + begin + // CTUN выключен: перестраиваем DDC на новую частоту + FCenterFreq := ActiveFreq; + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + end; + + // Переключаем кнопку диапазона по новой частоте + NewBand := FreqToBandIdx(ActiveFreq); + if (NewBand >= 0) and (NewBand <> FCurrentBand) then + begin + StyleButton(BtnBand[FCurrentBand], False); + FCurrentBand := NewBand; + StyleButton(BtnBand[FCurrentBand], True); + end; + + // Инвалидируем кэш линейки + FRulerLastFreq := -1.0; + FRulerLastVfo := -1.0; + + DrawSpectrum; + PbSpectrum.Invalidate; + DrawWaterfall; + PbWaterfall.Invalidate; + if PbRuler <> nil then PbRuler.Invalidate; +end; + +procedure TMainForm.FreqDispAClick(Sender: TObject); +begin + // Клик на VFO-A → активируем A (перестройка на FVfoA) + if FActiveVfo <> 0 then + ActivateVfo(0); +end; + +procedure TMainForm.FreqDispBClick(Sender: TObject); +begin + // Клик на VFO-B → активируем B (перестройка на FVfoB) + if FActiveVfo <> 1 then + ActivateVfo(1); +end; + procedure TMainForm.FreqDispAChanged(Sender: TObject; NewFreq: Int64); begin - ApplyVfoA(NewFreq); + if FActiveVfo = 0 then + ApplyVfoA(NewFreq) + else + FVfoA := NewFreq; // просто сохраняем, приёмник не перестраиваем end; procedure TMainForm.FreqDispBChanged(Sender: TObject; NewFreq: Int64); +var + BandIdx: Integer; begin FVfoB := NewFreq; if FActiveVfo = 1 then begin - FCenterFreq := FVfoB; + // VFO-B активен: перестраиваем приёмник FCenterFreq := FVfoB; + if FWDSPReady then FDSPEngine.SetShift(0.0); if FNetwork.Connected and FNetwork.Running then begin - FNetwork.UpdateState(FVfoB, FVfoB, FDriveLevel, FTransmitting, True, True); + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); FNetwork.SendFullHP; end; + // Обновляем диапазон + BandIdx := FreqToBandIdx(FVfoB); + if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + begin + StyleButton(BtnBand[FCurrentBand], False); + FCurrentBand := BandIdx; + StyleButton(BtnBand[FCurrentBand], True); + end; + FRulerLastFreq := -1.0; FRulerLastVfo := -1.0; + DrawSpectrum; + PbSpectrum.Invalidate; + if PbRuler <> nil then PbRuler.Invalidate; end; end; @@ -3464,6 +3628,7 @@ var FiltHi: Double; HalfSpan: Double; Scrolled: Boolean; + BandIdx: Integer; begin FVfoA := NewFreq; Scrolled := False; @@ -3513,6 +3678,15 @@ begin // Обновляем VFO дисплей FreqDispA.Frequency := Round(FVfoA); + // Обновляем кнопку диапазона если частота перешла в другой диапазон + BandIdx := FreqToBandIdx(FVfoA); + if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + begin + StyleButton(BtnBand[FCurrentBand], False); + FCurrentBand := BandIdx; + StyleButton(BtnBand[FCurrentBand], True); + end; + // DDC: передаём FCenterFreq (при CTUN OFF = FVfoA, при CTUN ON = фиксирован) if FNetwork.Connected and FNetwork.Running then begin @@ -3582,13 +3756,37 @@ procedure TMainForm.DoSpectrumClick(PixelX: Integer; PanelWidth: Integer); var ClickFreq: Int64; StepHz: Int64; + BandIdx: Integer; begin if PanelWidth <= 0 then Exit; StepHz := 100; - // Пиксель → частота через текущий центр дисплея (FCenterFreq) ClickFreq := Round(FCenterFreq + (PixelX / PanelWidth - 0.5) * FSpanHz); ClickFreq := (ClickFreq div StepHz) * StepHz; - ApplyVfoA(ClickFreq); + if FActiveVfo = 0 then + ApplyVfoA(ClickFreq) + else + begin + FVfoB := ClickFreq; + FreqDispB.Frequency := Round(FVfoB); + FCenterFreq := ClickFreq; + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + BandIdx := FreqToBandIdx(FVfoB); + if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + begin + StyleButton(BtnBand[FCurrentBand], False); + FCurrentBand := BandIdx; + StyleButton(BtnBand[FCurrentBand], True); + end; + FRulerLastFreq := -1.0; FRulerLastVfo := -1.0; + DrawSpectrum; + PbSpectrum.Invalidate; + if PbRuler <> nil then PbRuler.Invalidate; + end; end; procedure TMainForm.DoSpectrumDrag(PixelX: Integer; PanelWidth: Integer); @@ -3602,21 +3800,40 @@ begin if FCTun then begin // CTUN ON: drag двигает окно просмотра (FCenterFreq/DDC) - // VFO остаётся, shift обновляется = FVfoA - новый FCenterFreq + // VFO остаётся, shift обновляется = VFO - новый FCenterFreq FCenterFreq := FSpecDragFreq - dFreq; if FWDSPReady then - FDSPEngine.SetShift(FVfoA - FCenterFreq); + begin + if FActiveVfo = 0 then + FDSPEngine.SetShift(FVfoA - FCenterFreq) + else + FDSPEngine.SetShift(FVfoB - FCenterFreq); + end; if FNetwork.Connected and FNetwork.Running then begin FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); FNetwork.SendFullHP; end; - FSpectrumDirty := True; // таймер перерисует в ближайшем тике + FSpectrumDirty := True; end else begin - // CTUN OFF: drag двигает VFO, FCenterFreq следует через ApplyVfoA - ApplyVfoA(Round(FSpecDragFreq - dFreq)); + // CTUN OFF: drag двигает VFO, FCenterFreq следует через Apply + if FActiveVfo = 0 then + ApplyVfoA(Round(FSpecDragFreq - dFreq)) + else + begin + FVfoB := FSpecDragFreq - dFreq; + FreqDispB.Frequency := Round(FVfoB); + FCenterFreq := FVfoB; + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + FSpectrumDirty := True; + end; end; end; @@ -3636,7 +3853,10 @@ begin FSpecDrag := True; FSpecDragX0 := X; if FCTun then FSpecDragFreq := FCenterFreq - else FSpecDragFreq := FVfoA; + else begin + if FActiveVfo = 0 then FSpecDragFreq := FVfoA + else FSpecDragFreq := FVfoB; + end; PbSpectrum.Cursor := crSizeWE; end else if Button = mbRight then @@ -3698,7 +3918,10 @@ begin FSpecDrag := True; FSpecDragX0 := X; if FCTun then FSpecDragFreq := FCenterFreq - else FSpecDragFreq := FVfoA; + else begin + if FActiveVfo = 0 then FSpecDragFreq := FVfoA + else FSpecDragFreq := FVfoB; + end; PbWaterfall.Cursor := crSizeWE; end else if Button = mbRight then @@ -3812,23 +4035,76 @@ begin end; procedure TMainForm.BtnVfoSwapClick(Sender: TObject); -var Tmp: Double; +var + Tmp: Double; + BandIdx: Integer; + ActiveFreq: Int64; begin + // Меняем частоты местами Tmp := FVfoA; FVfoA := FVfoB; FVfoB := Tmp; + + // Определяем новую частоту активного VFO + if FActiveVfo = 0 then ActiveFreq := Round(FVfoA) + else ActiveFreq := Round(FVfoB); + + // Перестраиваем приёмник + FCenterFreq := ActiveFreq; + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + + // Обновляем кнопку диапазона + BandIdx := FreqToBandIdx(ActiveFreq); + if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then + begin + StyleButton(BtnBand[FCurrentBand], False); + FCurrentBand := BandIdx; + StyleButton(BtnBand[FCurrentBand], True); + end; + + FRulerLastFreq := -1.0; FRulerLastVfo := -1.0; UpdateVfoDisplay; - UpdateFilterButtons; - if FRunning then FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel); + DrawSpectrum; + PbSpectrum.Invalidate; + if PbRuler <> nil then PbRuler.Invalidate; end; procedure TMainForm.BtnVfoACopyBClick(Sender: TObject); begin - FVfoB := FVfoA; UpdateVfoDisplay; + // A>B: копируем частоту A в B (VFO-B получает частоту VFO-A) + FVfoB := FVfoA; + if FActiveVfo = 1 then + begin + // B активен — перестраиваем приёмник на новую частоту B + FCenterFreq := Round(FVfoB); + if FWDSPReady then FDSPEngine.SetShift(0.0); + if FNetwork.Connected and FNetwork.Running then + begin + FNetwork.UpdateState(FCenterFreq, FCenterFreq, FDriveLevel, FTransmitting, True, True); + FNetwork.SendFullHP; + end; + FRulerLastFreq := -1.0; FRulerLastVfo := -1.0; + DrawSpectrum; PbSpectrum.Invalidate; + if PbRuler <> nil then PbRuler.Invalidate; + end; + FreqDispB.Frequency := Round(FVfoB); + UpdateVfoDisplay; end; procedure TMainForm.BtnVfoBCopyAClick(Sender: TObject); begin - FVfoA := FVfoB; UpdateVfoDisplay; - if FRunning then FNetwork.SetRunAndFreq(True, FCenterFreq, FCenterFreq, FDriveLevel); + // B>A: копируем частоту B в A (VFO-A получает частоту VFO-B) + FVfoA := FVfoB; + if FActiveVfo = 0 then + ApplyVfoA(Round(FVfoA)) // A активен — перестраиваем приёмник + else + begin + FreqDispA.Frequency := Round(FVfoA); + UpdateVfoDisplay; + end; end; procedure TMainForm.BtnMOXClick(Sender: TObject);