mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +00:00
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>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6edcf8bdf4
commit
908c02d08a
+30
-160
@@ -1394,6 +1394,25 @@ begin
|
|||||||
FSyncingFromController := True;
|
FSyncingFromController := True;
|
||||||
try
|
try
|
||||||
case Field of
|
case Field of
|
||||||
|
rfVfoA:
|
||||||
|
begin
|
||||||
|
FreqDispA.Frequency := Round(FController.FVfoA);
|
||||||
|
// Не рисуем синхронно из wheel/drag: частые события забивают UI-поток.
|
||||||
|
// Invalidate лишь планирует; DrawSpectrum откладываем (FSpectrumDirty).
|
||||||
|
// Линейку дёргаем сразу — её рендер дешёвый, иначе отстаёт от спектра.
|
||||||
|
SyncSpecViewFreq;
|
||||||
|
FSpectrumDirty := True;
|
||||||
|
PbSpectrum.Invalidate;
|
||||||
|
if PbRuler <> nil then PbRuler.Invalidate;
|
||||||
|
if FController.FLastVfoScrolled then
|
||||||
|
begin
|
||||||
|
FWaterfallDirty := True;
|
||||||
|
if PbWaterfall <> nil then PbWaterfall.Invalidate;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
rfBand:
|
||||||
|
for i := 0 to BAND_COUNT - 1 do
|
||||||
|
StyleButton(BtnBand[i], i = FController.FCurrentBand);
|
||||||
rfMute:
|
rfMute:
|
||||||
begin
|
begin
|
||||||
StyleButton(BtnMute, FController.FMuted);
|
StyleButton(BtnMute, FController.FMuted);
|
||||||
@@ -4616,88 +4635,15 @@ end;
|
|||||||
// hits the edge of the display")
|
// hits the edge of the display")
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
procedure TMainForm.ApplyVfoA(NewFreq: Int64);
|
procedure TMainForm.ApplyVfoA(NewFreq: Int64);
|
||||||
var
|
|
||||||
Offset: Double;
|
|
||||||
FiltLo: Double;
|
|
||||||
FiltHi: Double;
|
|
||||||
HalfSpan: Double;
|
|
||||||
Scrolled: Boolean;
|
|
||||||
BandIdx: Integer;
|
|
||||||
begin
|
begin
|
||||||
// Клэмп в пределах XVTR-band — не даём VFO уйти за FreqBegin/FreqEnd
|
// Движок (XVTR-клэмп, CTUN shift/center/scroll, детект диапазона, FM авто-RPT,
|
||||||
if (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and
|
// сеть) — в контроллере SetVfoA; рендер FreqDisp/спектра/band-кнопок — в
|
||||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled then
|
// OnControllerState(rfVfoA/rfBand).
|
||||||
begin
|
FController.SetVfoA(NewFreq);
|
||||||
if NewFreq < FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin then
|
|
||||||
NewFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqBegin);
|
|
||||||
if NewFreq > FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd then
|
|
||||||
NewFreq := Round(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].FreqEnd);
|
|
||||||
end;
|
|
||||||
FController.FVfoA := NewFreq;
|
|
||||||
Scrolled := False;
|
|
||||||
|
|
||||||
if not FController.FCTun then
|
// Канальная оркестрация (UI). Только при отсутствии активного канала.
|
||||||
begin
|
// Авто-RPT сбрасывает сам SetVfoA через UpdateRptAutoState; здесь — авто-CTCSS
|
||||||
// ---- CTUN OFF ----
|
// и восстановление pre-channel drive (виджет TrkDrive).
|
||||||
// DDC = VFO, сдвига нет
|
|
||||||
FController.FCenterFreq := FController.FVfoA;
|
|
||||||
if FController.FWDSPReady then
|
|
||||||
FController.FDSPEngine.SetShift(0.0);
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
// ---- CTUN ON ----
|
|
||||||
// DDC (FController.FCenterFreq) стоит на месте.
|
|
||||||
// SetRXAShiftFreq сдвигает спектр внутри WDSP так чтобы
|
|
||||||
// демодулятор принимал сигнал на FController.FVfoA, а не на FController.FCenterFreq.
|
|
||||||
// Shift = FController.FVfoA - FController.FCenterFreq (в Гц)
|
|
||||||
Offset := FController.FVfoA - FController.FCenterFreq;
|
|
||||||
if FController.FWDSPReady then
|
|
||||||
FController.FDSPEngine.SetShift(Offset);
|
|
||||||
|
|
||||||
// Проверяем не вышла ли полоса фильтра за край дисплея
|
|
||||||
HalfSpan := FController.FSpanHz / 2;
|
|
||||||
case FController.FMode of
|
|
||||||
0: begin FiltLo := Offset - FController.FFilterBW; FiltHi := Offset - 100; end;
|
|
||||||
1: begin FiltLo := Offset + 100; FiltHi := Offset + FController.FFilterBW; end;
|
|
||||||
else begin FiltLo := Offset - FController.FFilterBW/2; FiltHi := Offset + FController.FFilterBW/2; end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if (FiltHi > HalfSpan) or (FiltLo < -HalfSpan) then
|
|
||||||
begin
|
|
||||||
// Полоса вышла за край → прокручиваем центр
|
|
||||||
if FiltHi > HalfSpan then
|
|
||||||
FController.FCenterFreq := FController.FVfoA - HalfSpan * 0.5
|
|
||||||
else
|
|
||||||
FController.FCenterFreq := FController.FVfoA + HalfSpan * 0.5;
|
|
||||||
// Пересчитываем сдвиг после прокрутки
|
|
||||||
if FController.FWDSPReady then
|
|
||||||
FController.FDSPEngine.SetShift(FController.FVfoA - FController.FCenterFreq);
|
|
||||||
// DDC перестраивается на новый FController.FCenterFreq
|
|
||||||
Scrolled := True;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Обновляем VFO дисплей
|
|
||||||
FreqDispA.Frequency := Round(FController.FVfoA);
|
|
||||||
|
|
||||||
// Обновляем кнопку диапазона если частота перешла в другой диапазон.
|
|
||||||
// В XVTR-режиме HF band-кнопки не подсвечиваем (мы клэмпим VFO внутри
|
|
||||||
// XVTR-диапазона — FreqToBandIdx всегда вернёт -1 для VHF/UHF).
|
|
||||||
if FController.FCurrentXvtr < 0 then
|
|
||||||
begin
|
|
||||||
BandIdx := FreqToBandIdx(FController.FVfoA);
|
|
||||||
if (BandIdx >= 0) and (BandIdx <> FController.FCurrentBand) then
|
|
||||||
begin
|
|
||||||
StyleButton(BtnBand[FController.FCurrentBand], False);
|
|
||||||
FController.FCurrentBand := BandIdx;
|
|
||||||
StyleButton(BtnBand[FController.FCurrentBand], True);
|
|
||||||
FController.FDriveLevel := CalcDriveByte;
|
|
||||||
end;
|
|
||||||
end;
|
|
||||||
|
|
||||||
// Если нет активного канала, но CTCSS/RPT были установлены каналом (авто-флаги),
|
|
||||||
// сбрасываем их при первом же изменении частоты
|
|
||||||
if FActiveChannelIdx < 0 then
|
if FActiveChannelIdx < 0 then
|
||||||
begin
|
begin
|
||||||
if FController.FFMCTCSSAutoActive then
|
if FController.FFMCTCSSAutoActive then
|
||||||
@@ -4707,13 +4653,6 @@ begin
|
|||||||
if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, False);
|
if BtnFMCTCSS <> nil then StyleButton(BtnFMCTCSS, False);
|
||||||
if FController.FWDSPReady then FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]);
|
if FController.FWDSPReady then FController.FDSPEngine.SetTXCTCSS(False, CTCSS_TONES[FController.FFMCTCSSToneIdx]);
|
||||||
end;
|
end;
|
||||||
if FController.FFMRptAutoActive then
|
|
||||||
begin
|
|
||||||
FController.FFMRptDir := RPT_NONE;
|
|
||||||
FController.FFMRptAutoActive := False;
|
|
||||||
if BtnFMRptMinus <> nil then StyleButton(BtnFMRptMinus, False);
|
|
||||||
if BtnFMRptPlus <> nil then StyleButton(BtnFMRptPlus, False);
|
|
||||||
end;
|
|
||||||
if FPreChannelPower >= 0 then
|
if FPreChannelPower >= 0 then
|
||||||
begin
|
begin
|
||||||
TrkDrive.Position := FPreChannelPower;
|
TrkDrive.Position := FPreChannelPower;
|
||||||
@@ -4721,34 +4660,10 @@ begin
|
|||||||
FController.FDriveLevel := CalcDriveByte;
|
FController.FDriveLevel := CalcDriveByte;
|
||||||
if FController.FWDSPReady then
|
if FController.FWDSPReady then
|
||||||
FController.FDSPEngine.SetDriveLevel(FController.FDrivePercent / 100.0);
|
FController.FDSPEngine.SetDriveLevel(FController.FDrivePercent / 100.0);
|
||||||
|
FController.PushNetworkState; // протолкнуть восстановленный drive
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// Auto-enable repeater minus for 2 m repeater input segment
|
|
||||||
if FController.FMode = MODE_FM then UpdateRptAutoState;
|
|
||||||
|
|
||||||
// DDC: передаём FController.FCenterFreq (при CTUN OFF = FController.FVfoA, при CTUN ON = фиксирован)
|
|
||||||
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;
|
|
||||||
|
|
||||||
// Не рисуем синхронно из wheel/drag path: частые события мыши иначе
|
|
||||||
// забивают UI-поток и мешают аудио. Таймер подхватит ближайший кадр.
|
|
||||||
// Сам DrawSpectrum/DrawWaterfall откладываем (FSpectrumDirty), но Invalidate
|
|
||||||
// дёргаем сразу — он лишь планирует перерисовку. Линейку (PbRuler) тоже:
|
|
||||||
// её рендер дешёвый и без него она отставала от спектра при клике/смене VFO.
|
|
||||||
SyncSpecViewFreq;
|
|
||||||
FSpectrumDirty := True;
|
|
||||||
PbSpectrum.Invalidate;
|
|
||||||
if PbRuler <> nil then PbRuler.Invalidate;
|
|
||||||
if Scrolled then
|
|
||||||
begin
|
|
||||||
FWaterfallDirty := True;
|
|
||||||
PbWaterfall.Invalidate;
|
|
||||||
end;
|
|
||||||
|
|
||||||
CheckChannelActive;
|
CheckChannelActive;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -6559,55 +6474,10 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
function TMainForm.CalcDriveByte: Byte;
|
function TMainForm.CalcDriveByte: Byte;
|
||||||
// Thetis-compatible formula:
|
|
||||||
// drive_byte = int( min(target_volts/0.8, 1.0) * 1.02 * 255 )
|
|
||||||
// where:
|
|
||||||
// target_volts = sqrt( 10^((target_dBm)/10) * 0.05 ) [E=sqrt(P*R), R=50, P in mW]
|
|
||||||
// target_dBm = 10*log10(pwr_W * 1000) - Cal
|
|
||||||
// pwr_W = Pos/100 * FController.FPAMaxPower
|
|
||||||
// Cal = per-band PA gain in dB (HF: ~38.8-41.3, VHF default: ~56.2)
|
|
||||||
var
|
|
||||||
Cal: Double;
|
|
||||||
Pos: Integer;
|
|
||||||
XvtrActive: Boolean;
|
|
||||||
pwr_W: Double;
|
|
||||||
target_dBm: Double;
|
|
||||||
target_volts: Double;
|
|
||||||
audio_vol: Double;
|
|
||||||
begin
|
begin
|
||||||
XvtrActive := (FController.FCurrentXvtr >= 0) and (FController.FCurrentXvtr < CFG_XVTR_COUNT) and
|
// Формула — в контроллере (чистая над состоянием). Делегат для существующих
|
||||||
FController.FXvtrSettings.Entries[FController.FCurrentXvtr].Enabled;
|
// вызовов MainForm.
|
||||||
|
Result := FController.CalcDriveByte;
|
||||||
if XvtrActive then
|
|
||||||
Cal := FController.FVHFBandCal[FController.FCurrentXvtr]
|
|
||||||
else if (FController.FCurrentBand >= 0) and (FController.FCurrentBand < BAND_COUNT) then
|
|
||||||
Cal := FController.FPABandCal[FController.FCurrentBand]
|
|
||||||
else
|
|
||||||
Cal := 38.8;
|
|
||||||
|
|
||||||
// Pos — итоговый процент драйва 0..100
|
|
||||||
if FController.FTuning then
|
|
||||||
begin
|
|
||||||
if XvtrActive and FController.FXvtrSettings.UseXVTRTunePower then
|
|
||||||
Pos := EnsureRange(FController.FXvtrSettings.Entries[FController.FCurrentXvtr].TXPower, 0, 100)
|
|
||||||
else
|
|
||||||
Pos := EnsureRange(FController.FTXSettings.TUNLevel, 0, 100);
|
|
||||||
end
|
|
||||||
else
|
|
||||||
begin
|
|
||||||
if XvtrActive then
|
|
||||||
Pos := Round(FController.FDrivePercent * FController.FXvtrSettings.Entries[FController.FCurrentXvtr].TXPower / 100.0)
|
|
||||||
else
|
|
||||||
Pos := FController.FDrivePercent;
|
|
||||||
end;
|
|
||||||
|
|
||||||
if Pos <= 0 then begin Result := 0; Exit; end;
|
|
||||||
|
|
||||||
pwr_W := Pos / 100.0 * FController.FPAMaxPower;
|
|
||||||
target_dBm := 10.0 * Log10(pwr_W * 1000.0) - Cal;
|
|
||||||
target_volts := Sqrt(Power(10.0, target_dBm * 0.1) * 0.05);
|
|
||||||
audio_vol := Min(target_volts / 0.8, 1.0);
|
|
||||||
Result := Min(Round(audio_vol * 1.02 * 255.0), 255);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer);
|
procedure TMainForm.OnTXIQReady(const Buf: array of Double; Count: Integer);
|
||||||
|
|||||||
+130
-4
@@ -47,10 +47,10 @@ unit RadioController;
|
|||||||
interface
|
interface
|
||||||
|
|
||||||
uses
|
uses
|
||||||
Classes, SysUtils,
|
Classes, SysUtils, Math,
|
||||||
HPSDRProtocol, HPSDRNetwork,
|
HPSDRProtocol, HPSDRNetwork,
|
||||||
WDSPEngine, AudioOutput, AudioInput,
|
WDSPEngine, AudioOutput, AudioInput,
|
||||||
Settings, ChannelStore, FMRepeater;
|
Settings, ChannelStore, FMRepeater, BoardUtils;
|
||||||
|
|
||||||
type
|
type
|
||||||
// Поле, которое изменилось — для гранулярных уведомлений наружу.
|
// Поле, которое изменилось — для гранулярных уведомлений наружу.
|
||||||
@@ -110,7 +110,6 @@ type
|
|||||||
procedure Changed(Field: TRadioField);
|
procedure Changed(Field: TRadioField);
|
||||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||||
procedure PushNetworkState; // полный HP-кадр в радио (RX/TX freq+drive)
|
|
||||||
public
|
public
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm).
|
// ПУБЛИЧНЫЕ ПОЛЯ (миграционные — имена совпадают с TMainForm).
|
||||||
@@ -147,6 +146,7 @@ type
|
|||||||
FMuted: Boolean;
|
FMuted: Boolean;
|
||||||
FDrivePercent: Integer; // драйв 0..100 % (UI-уровень, источник истины)
|
FDrivePercent: Integer; // драйв 0..100 % (UI-уровень, источник истины)
|
||||||
FDriveLevel: Byte; // вычисляемый hardware-байт (CalcDriveByte)
|
FDriveLevel: Byte; // вычисляемый hardware-байт (CalcDriveByte)
|
||||||
|
FLastVfoScrolled: Boolean; // последний SetVfoA прокрутил центр (для водопада)
|
||||||
FAtten: Integer;
|
FAtten: Integer;
|
||||||
FPAMaxPower: Double;
|
FPAMaxPower: Double;
|
||||||
FRunning: Boolean;
|
FRunning: Boolean;
|
||||||
@@ -216,6 +216,14 @@ type
|
|||||||
// FM-девиация, squelch). Вызывается командами и UI-путями смены режима.
|
// FM-девиация, squelch). Вызывается командами и UI-путями смены режима.
|
||||||
procedure ApplyModeFilter;
|
procedure ApplyModeFilter;
|
||||||
|
|
||||||
|
// Вычисляет hardware drive-байт (Thetis-формула) из текущего состояния
|
||||||
|
// (band/xvtr cal, TUN/drive%, PA max). Чистая функция над состоянием.
|
||||||
|
function CalcDriveByte: Byte;
|
||||||
|
|
||||||
|
// Полный HP-кадр в радио (RX/TX freq через трансвертор + drive + MOX).
|
||||||
|
// Вызывается командами смены частоты/центра и UI после правки drive.
|
||||||
|
procedure PushNetworkState;
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// КОМАНДЫ — единый API для GUI / Web / CAT / Arduino.
|
// КОМАНДЫ — единый API для GUI / Web / CAT / Arduino.
|
||||||
// Фаза 1: обновляют поле + шлют событие; логика движка — TODO-wiring.
|
// Фаза 1: обновляют поле + шлют событие; логика движка — TODO-wiring.
|
||||||
@@ -441,8 +449,126 @@ end;
|
|||||||
// Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm.
|
// Команды (Фаза 1 — каркас). Полная логика переносится из TMainForm.
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|
||||||
|
function TRadioController.CalcDriveByte: Byte;
|
||||||
|
// Thetis-совместимая формула:
|
||||||
|
// drive_byte = int( min(target_volts/0.8, 1.0) * 1.02 * 255 )
|
||||||
|
// target_volts = sqrt( 10^(target_dBm/10) * 0.05 )
|
||||||
|
// target_dBm = 10*log10(pwr_W*1000) - Cal ; pwr_W = Pos/100 * FPAMaxPower
|
||||||
|
var
|
||||||
|
Cal: Double;
|
||||||
|
Pos: Integer;
|
||||||
|
XvtrActive: Boolean;
|
||||||
|
pwr_W: Double;
|
||||||
|
target_dBm: Double;
|
||||||
|
target_volts: Double;
|
||||||
|
audio_vol: Double;
|
||||||
|
begin
|
||||||
|
XvtrActive := (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||||
|
FXvtrSettings.Entries[FCurrentXvtr].Enabled;
|
||||||
|
if XvtrActive then
|
||||||
|
Cal := FVHFBandCal[FCurrentXvtr]
|
||||||
|
else if (FCurrentBand >= 0) and (FCurrentBand < CFG_BAND_COUNT) then
|
||||||
|
Cal := FPABandCal[FCurrentBand]
|
||||||
|
else
|
||||||
|
Cal := 38.8;
|
||||||
|
|
||||||
|
if FTuning then
|
||||||
|
begin
|
||||||
|
if XvtrActive and FXvtrSettings.UseXVTRTunePower then
|
||||||
|
Pos := EnsureRange(FXvtrSettings.Entries[FCurrentXvtr].TXPower, 0, 100)
|
||||||
|
else
|
||||||
|
Pos := EnsureRange(FTXSettings.TUNLevel, 0, 100);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
if XvtrActive then
|
||||||
|
Pos := Round(FDrivePercent * FXvtrSettings.Entries[FCurrentXvtr].TXPower / 100.0)
|
||||||
|
else
|
||||||
|
Pos := FDrivePercent;
|
||||||
|
end;
|
||||||
|
|
||||||
|
if Pos <= 0 then begin Result := 0; Exit; end;
|
||||||
|
|
||||||
|
pwr_W := Pos / 100.0 * FPAMaxPower;
|
||||||
|
target_dBm := 10.0 * Log10(pwr_W * 1000.0) - Cal;
|
||||||
|
target_volts := Sqrt(Power(10.0, target_dBm * 0.1) * 0.05);
|
||||||
|
audio_vol := Min(target_volts / 0.8, 1.0);
|
||||||
|
Result := Min(Round(audio_vol * 1.02 * 255.0), 255);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetVfoA(Hz: Double);
|
procedure TRadioController.SetVfoA(Hz: Double);
|
||||||
begin FVfoA := Hz; Changed(rfVfoA); { TODO wiring: ApplyVfoA } end;
|
// Чистое ядро перестройки VFO A: XVTR-клэмп, CTUN shift/center (с прокруткой
|
||||||
|
// центра при выходе полосы фильтра за край), детект диапазона, FM авто-RPT,
|
||||||
|
// проталкивание в сеть. Рендер (FreqDisp/спектр/band-кнопки) — в UI через
|
||||||
|
// OnControllerState(rfVfoA/rfBand). Канальную оркестрацию (авто-CTCSS,
|
||||||
|
// pre-channel drive) делает вызывающий UI-путь.
|
||||||
|
var
|
||||||
|
NewFreq: Int64;
|
||||||
|
Offset: Double;
|
||||||
|
FiltLo, FiltHi, HalfSpan: Double;
|
||||||
|
BandIdx: Integer;
|
||||||
|
begin
|
||||||
|
NewFreq := Round(Hz);
|
||||||
|
// Клэмп в пределах XVTR-band — не даём VFO уйти за FreqBegin/FreqEnd
|
||||||
|
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) and
|
||||||
|
FXvtrSettings.Entries[FCurrentXvtr].Enabled then
|
||||||
|
begin
|
||||||
|
if NewFreq < FXvtrSettings.Entries[FCurrentXvtr].FreqBegin then
|
||||||
|
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqBegin);
|
||||||
|
if NewFreq > FXvtrSettings.Entries[FCurrentXvtr].FreqEnd then
|
||||||
|
NewFreq := Round(FXvtrSettings.Entries[FCurrentXvtr].FreqEnd);
|
||||||
|
end;
|
||||||
|
FVfoA := NewFreq;
|
||||||
|
FLastVfoScrolled := False;
|
||||||
|
|
||||||
|
if not FCTun then
|
||||||
|
begin
|
||||||
|
// CTUN OFF: DDC = VFO, сдвига нет
|
||||||
|
FCenterFreq := FVfoA;
|
||||||
|
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
begin
|
||||||
|
// CTUN ON: DDC фиксирован, демодулятор сдвигаем на FVfoA-FCenterFreq
|
||||||
|
Offset := FVfoA - FCenterFreq;
|
||||||
|
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(Offset);
|
||||||
|
// Полоса фильтра вышла за край дисплея → прокручиваем центр
|
||||||
|
HalfSpan := FSpanHz / 2;
|
||||||
|
case FMode of
|
||||||
|
0: begin FiltLo := Offset - FFilterBW; FiltHi := Offset - 100; end;
|
||||||
|
1: begin FiltLo := Offset + 100; FiltHi := Offset + FFilterBW; end;
|
||||||
|
else begin FiltLo := Offset - FFilterBW/2; FiltHi := Offset + FFilterBW/2; end;
|
||||||
|
end;
|
||||||
|
if (FiltHi > HalfSpan) or (FiltLo < -HalfSpan) then
|
||||||
|
begin
|
||||||
|
if FiltHi > HalfSpan then
|
||||||
|
FCenterFreq := FVfoA - HalfSpan * 0.5
|
||||||
|
else
|
||||||
|
FCenterFreq := FVfoA + HalfSpan * 0.5;
|
||||||
|
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(FVfoA - FCenterFreq);
|
||||||
|
FLastVfoScrolled := True;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Детект диапазона (только HF: в XVTR клэмп держит VFO внутри band,
|
||||||
|
// FreqToBandIdx вернёт -1 для VHF/UHF).
|
||||||
|
if FCurrentXvtr < 0 then
|
||||||
|
begin
|
||||||
|
BandIdx := FreqToBandIdx(FVfoA);
|
||||||
|
if (BandIdx >= 0) and (BandIdx <> FCurrentBand) then
|
||||||
|
begin
|
||||||
|
FCurrentBand := BandIdx;
|
||||||
|
FDriveLevel := CalcDriveByte;
|
||||||
|
Changed(rfBand);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
|
// Авто-MINUS в 2 м репитерном сегменте (само снимает при уходе из сегмента).
|
||||||
|
if FMode = MODE_FM then UpdateRptAutoState;
|
||||||
|
|
||||||
|
PushNetworkState;
|
||||||
|
Changed(rfVfoA);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TRadioController.SetVfoB(Hz: Double);
|
procedure TRadioController.SetVfoB(Hz: Double);
|
||||||
begin FVfoB := Hz; Changed(rfVfoB); { TODO wiring } end;
|
begin FVfoB := Hz; Changed(rfVfoB); { TODO wiring } end;
|
||||||
|
|||||||
Reference in New Issue
Block a user