mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(filter): произвольные кромки фильтра — таблица по режиму, редактор, drag, TX-полоска
Фильтр главного приёмника перестал быть одним числом. Теперь это таблица (Name, Low, High) на режим: 10 пресетов + VAR1/VAR2, знаковые кромки (у LSB своя строка, у USB своя), паритет с Thetis FilterForm. Слайсы независимые кромки умели и раньше — главный приёмник настраивался только пресетами. Модель (Settings/RadioController): - TFilterSlot/TFilterSet/TFilterTable + заводские дефолты из прежних FILT_*_BW и FILT_*_NAMES (переехали в Settings — из них строится таблица). - FFilterLo/FFilterHi = истина, FFilterBW производная (FilterBWFromEdges), поэтому все прежние читатели ширины (web, зум, band-cache, CAT) не тронуты. - ApplyFilterSlot — единственная точка «слот → состояние тракта». - SetFilterEdges: произвольные кромки уходят в VAR1 с автопереключением, пресеты нельзя испортить случайным движением мыши или энкодера. - Персист: секция "filters" под MAC, пишутся только режимы, отличные от заводских; секция режима сносится целиком, иначе возврат слота к заводскому оставлял бы старый ключ. - Диапазон помнит только выбранный слот: кромки живут в таблице, второго источника истины у VAR нет. UI: - 12 кнопок фильтра (2 ряда по 6), подписи из живой таблицы. - FilterPopup — поповер редактора по ПКМ на кнопке фильтра, по образцу PureSignalPopup (дочерний контрол формы, не top-level: Wayland). Слева слоты, справа Name/Low/High/Width, внизу форма полосы на знаковой оси. - Перетаскивание края полосы на спектре: ±5px, снап 10 Гц, Alt = симметрично (не Ctrl — он создаёт слайс). Отказ на передаче и когда полоса на экране уже 12px, иначе захват съедал бы клик-тюн. Отрисовка: - Полоса главного VFO рисуется по РЕАЛЬНЫМ кромкам приёмника, слайса — по его кромкам; вывод из режима и ширины остался фолбэком. Пара→ширина→пара было преобразованием с потерями: несимметричный фильтр из слайс-CAT (ZZFL/ZZFH) рисовался не там, где звучал. - На передаче полоса считается по TX-фильтру (TXSignedEdges), а не по приёмной ширине — TX-полоса от RX не зависит (модель Thetis). - CalcFilterBandXFor больше не держит свою копию таблицы знака боковой: единственная таблица теперь FilterEdgesFromBW. Управление: - CAT ZZFL/ZZFH заработали на основном порту (были заглушками). - Андромеда: энкодеры #4/#5 — независимые Filter High/Low, как на железе G2 (было: #4 ширина, #5 не реализован). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+176
-136
@@ -215,6 +215,11 @@ type
|
||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||
procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state)
|
||||
// Единственная точка, где выбранный слот превращается в состояние тракта
|
||||
// (FFilter/кромки/производная BW/FM-девиация). Тракт не трогает — только state.
|
||||
procedure ApplyFilterSlot(Idx: Integer);
|
||||
function ClampFilterIdx(Mode, Idx: Integer): Integer;
|
||||
procedure CacheFilterToBand; // фильтр текущего банда в band-cache
|
||||
function ActiveVfoHz: Double; // частота активного VFO (A или B)
|
||||
function AnchoredPan(AZoom: Double): Double; // пан, центрирующий окно на маркере VFO
|
||||
procedure ApplyTuneCore(VfoHz: Double); // CTUN shift/center/scroll для актив. VFO
|
||||
@@ -275,8 +280,14 @@ type
|
||||
FVfoA, FVfoB: Double;
|
||||
FActiveVfo: Integer;
|
||||
FMode: Integer;
|
||||
FFilter: Integer;
|
||||
FFilter: Integer; // слот 0..FILT_SLOTS-1 (10/11 = VAR1/VAR2)
|
||||
// Знаковые кромки текущего фильтра — ИСТИНА. FFilterBW производная
|
||||
// (FilterBWFromEdges) и оставлена ради прежних читателей (web, зум,
|
||||
// band-cache, CAT); писать в неё напрямую нельзя.
|
||||
FFilterLo: Integer;
|
||||
FFilterHi: Integer;
|
||||
FFilterBW: Integer;
|
||||
FFilterTable: TFilterTable; // редактируемые фильтры по режиму
|
||||
FAGCMode: Integer;
|
||||
FAGCTop: Integer;
|
||||
FRxGainMode: Integer; // Pluto hw-gain: 0=manual,1=fast,2=slow,3=hybrid
|
||||
@@ -670,6 +681,23 @@ type
|
||||
procedure SetSliceFilter(Id, Low, High: Integer);
|
||||
procedure SetSliceModeBW(Id, Mode, BW: Integer); // режим+полоса → кромки фильтра
|
||||
class procedure FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
// --- Редактируемая таблица фильтров (пресеты + VAR1/VAR2 на режим) ---
|
||||
function FilterSlot(Mode, Idx: Integer): TFilterSlot;
|
||||
procedure SetFilterSlot(Mode, Idx: Integer; const S: TFilterSlot);
|
||||
procedure ResetFilterSlot(Mode, Idx: Integer);
|
||||
procedure ResetFilterSet(Mode: Integer);
|
||||
// Произвольные кромки (drag на спектре, CAT ZZFL/ZZFH, web): пишутся в
|
||||
// VAR1 и переключают на него, если текущий слот — пресет. Пресеты таким
|
||||
// образом невозможно испортить случайным движением мыши.
|
||||
procedure SetFilterEdges(LoHz, HiHz: Integer);
|
||||
// Только state, без применения к тракту: UpdateFilterButtons — рендер,
|
||||
// DSP в его путях перестраивается отдельно (как было до таблицы).
|
||||
procedure SelectFilterSlotState(Idx: Integer);
|
||||
procedure SanitizeFilterState;
|
||||
// Знаковые края TX-полосы (для отрисовки полосы фильтра при передаче).
|
||||
procedure TXFilterEdges(out Lo, Hi: Integer);
|
||||
property FilterLo: Integer read FFilterLo;
|
||||
property FilterHi: Integer read FFilterHi;
|
||||
class function AGCModeFromUI(M: Integer): TWDSPAGCMode; // UI-индекс 0..4 → WDSP enum
|
||||
class function AGCModeToUI(A: TWDSPAGCMode): Integer; // и обратно
|
||||
procedure SetSliceAGCMode(Id: Integer; AGC: TWDSPAGCMode);
|
||||
@@ -1037,7 +1065,9 @@ begin
|
||||
FDeviceStore := TDeviceStore.Create; // общий список устройств (desktop+web)
|
||||
// Дефолты (дублируют TMainForm.FormCreate; в GUI перезапишутся, в демоне нужны).
|
||||
FVfoA := 14200000; FVfoB := 7100000; FActiveVfo := 0;
|
||||
FMode := MODE_USB; FFilter := 5; FFilterBW := 2700;
|
||||
FMode := MODE_USB; FFilter := 5;
|
||||
DefaultFilterTable(FFilterTable); // до подключения — заводские фильтры
|
||||
ApplyFilterSlot(FFilter);
|
||||
FAGCMode := 1; FAGCTop := 90;
|
||||
FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию
|
||||
FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX)
|
||||
@@ -3161,18 +3191,10 @@ end;
|
||||
|
||||
// Кромки полосы под режим/полосу (единый расчёт для главного канала и слайсов).
|
||||
class procedure TRadioController.FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
var Half: Integer;
|
||||
// Обёртка над единственной таблицей знака (Settings.FilterEdgesFromBW) —
|
||||
// оставлена ради прежних вызывающих (MainForm, SetSliceModeBW).
|
||||
begin
|
||||
Half := BW div 2;
|
||||
case Mode of
|
||||
MODE_LSB: begin Lo := -BW; Hi := -100; end;
|
||||
MODE_USB: begin Lo := 100; Hi := BW; end;
|
||||
// DIGI: полоса от нуля — поднесущие цифровых видов начинаются низко,
|
||||
// срез на 100 Гц как в SSB съел бы их.
|
||||
MODE_DIGL: begin Lo := -BW; Hi := 0; end;
|
||||
MODE_DIGU: begin Lo := 0; Hi := BW; end;
|
||||
else Lo := -Half; Hi := Half; // DSB/CW/FM/AM/SAM/WFM
|
||||
end;
|
||||
FilterEdgesFromBW(Mode, BW, Lo, Hi);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceModeBW(Id, Mode, BW: Integer);
|
||||
@@ -3189,7 +3211,10 @@ procedure TRadioController.ApplyModeFilter;
|
||||
var
|
||||
Lo, Hi: Integer;
|
||||
begin
|
||||
FilterEdgesFor(FMode, FFilterBW, Lo, Hi);
|
||||
// Кромки берём как есть: они уже знаковые и уже под текущий режим (таблица
|
||||
// фильтров ведётся ПО режиму), конвертировать боковую больше нечем и негде.
|
||||
Lo := FFilterLo;
|
||||
Hi := FFilterHi;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
begin
|
||||
// Только RX. TX-полоса НЕ следует за RX-фильтром (модель Thetis) — её
|
||||
@@ -3216,61 +3241,53 @@ end;
|
||||
// Выбирает дефолтный фильтр (индекс + полоса + FM-девиация) под текущий FMode.
|
||||
// Зеркалит state-часть UI UpdateFilterButtons, чтобы смена режима была
|
||||
// корректной и без UI (headless). FM сохраняет текущий NFM/WFM, прочие — дефолт.
|
||||
function TRadioController.ClampFilterIdx(Mode, Idx: Integer): Integer;
|
||||
// VAR1/VAR2 есть у любого режима, поэтому проверяем только пресетную часть.
|
||||
// Выход за таблицу режима возвращает ЗАВОДСКОЙ индекс (а не крайний) — так
|
||||
// вело себя прежнее «if FFilter > FILT_WFM_COUNT-1 then FILT_WFM_DEF».
|
||||
begin
|
||||
Result := Idx;
|
||||
if (Idx = FILT_VAR1) or (Idx = FILT_VAR2) then Exit;
|
||||
if (Idx < 0) or (Idx > FilterPresetCount(Mode) - 1) then
|
||||
Result := FilterDefIdx(Mode);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyFilterSlot(Idx: Integer);
|
||||
var S: TFilterSlot;
|
||||
begin
|
||||
FFilter := ClampFilterIdx(FMode, Idx);
|
||||
S := FFilterTable[FMode][FFilter];
|
||||
FFilterLo := S.Lo;
|
||||
FFilterHi := S.Hi;
|
||||
FFilterBW := FilterBWFromEdges(FMode, S.Lo, S.Hi);
|
||||
// FM-девиация привязана к пресету (NFM/WFM), у VAR берём девиацию дефолта.
|
||||
if FMode = MODE_FM then
|
||||
begin
|
||||
if FFilter <= 1 then FFMDeviation := FILT_FM_DEV[FFilter]
|
||||
else FFMDeviation := FILT_FM_DEV[FILT_FM_DEF];
|
||||
end
|
||||
else if FMode = MODE_DMR then
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyModeDefaults;
|
||||
begin
|
||||
// FM/WFM/DMR/FMRAW сохраняют выбранный пресет (NFM↔WFM и т.п.), остальные
|
||||
// режимы возвращаются к заводскому индексу — как было до таблицы фильтров.
|
||||
case FMode of
|
||||
MODE_FM:
|
||||
begin
|
||||
if FFilter > 1 then FFilter := FILT_FM_DEF;
|
||||
FFilterBW := FILT_FM_BW[FFilter];
|
||||
FFMDeviation := FILT_FM_DEV[FFilter];
|
||||
end;
|
||||
MODE_WFM:
|
||||
begin
|
||||
if FFilter > FILT_WFM_COUNT - 1 then FFilter := FILT_WFM_DEF;
|
||||
FFilterBW := FILT_WFM_BW[FFilter];
|
||||
end;
|
||||
MODE_DMR:
|
||||
begin
|
||||
FFilter := 0;
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
MODE_FMRAW:
|
||||
begin
|
||||
if FFilter > FILT_RAW_COUNT - 1 then FFilter := FILT_RAW_DEF;
|
||||
FFilterBW := FILT_RAW_BW[FFilter];
|
||||
end;
|
||||
MODE_LSB, MODE_USB:
|
||||
begin FFilter := FILT_SSB_DEF; FFilterBW := FILT_SSB_BW[FFilter]; end;
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
begin FFilter := FILT_DIGI_DEF; FFilterBW := FILT_DIGI_BW[FFilter]; end;
|
||||
MODE_DSB:
|
||||
begin FFilter := FILT_DSB_DEF; FFilterBW := FILT_DSB_BW[FFilter]; end;
|
||||
MODE_CWL, MODE_CWU:
|
||||
begin FFilter := FILT_CW_DEF; FFilterBW := FILT_CW_BW[FFilter]; end;
|
||||
else begin FFilter := FILT_AM_DEF; FFilterBW := FILT_AM_BW[FFilter]; end;
|
||||
MODE_FM, MODE_WFM, MODE_DMR, MODE_FMRAW: ApplyFilterSlot(FFilter);
|
||||
else ApplyFilterSlot(FilterDefIdx(FMode));
|
||||
end;
|
||||
end;
|
||||
|
||||
function TRadioController.FilterBWFor(M, Idx: Integer): Integer;
|
||||
// Полоса выбранного фильтра под режим. Зеркалит выбор таблицы в UpdateFilterButtons,
|
||||
// но НЕ меняет индекс (в отличие от ApplyModeDefaults) — нужно для XVTR-restore.
|
||||
var S: TFilterSlot;
|
||||
begin
|
||||
case M of
|
||||
MODE_FM: Result := FILT_FM_BW[EnsureRange(Idx, 0, FILT_FM_COUNT - 1)];
|
||||
MODE_WFM: Result := FILT_WFM_BW[EnsureRange(Idx, 0, FILT_WFM_COUNT - 1)];
|
||||
MODE_DMR: Result := FILT_DMR_BW;
|
||||
MODE_FMRAW: Result := FILT_RAW_BW[EnsureRange(Idx, 0, FILT_RAW_COUNT - 1)];
|
||||
MODE_LSB, MODE_USB:
|
||||
Result := FILT_SSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
Result := FILT_DIGI_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
MODE_DSB: Result := FILT_DSB_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
MODE_CWL, MODE_CWU:
|
||||
Result := FILT_CW_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
else Result := FILT_AM_BW[EnsureRange(Idx, 0, FILT_COUNT - 1)];
|
||||
end;
|
||||
M := EnsureRange(M, 0, CFG_MODE_MAX);
|
||||
S := FFilterTable[M][ClampFilterIdx(M, Idx)];
|
||||
Result := FilterBWFromEdges(M, S.Lo, S.Hi);
|
||||
end;
|
||||
|
||||
function TRadioController.FilterIdxFor(M, BW: Integer): Integer;
|
||||
@@ -3346,26 +3363,10 @@ begin
|
||||
end;
|
||||
|
||||
procedure TRadioController.SanitizeFilterForMode;
|
||||
// Для FM индексы фильтров только 0/1 (NFM/WFM); приводим FFilter и
|
||||
// синхронизируем FFilterBW/FFMDeviation. Чистая (state) часть UpdateFilterButtons.
|
||||
// У FM/DMR/FMRAW пресетов меньше, чем у SSB: приводим индекс к допустимому и
|
||||
// синхронизируем кромки/BW/девиацию. Чистая (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;
|
||||
if FMode = MODE_DMR then
|
||||
begin
|
||||
FFilter := 0;
|
||||
FFilterBW := FILT_DMR_BW;
|
||||
FFMDeviation := DMR_FM_DEV;
|
||||
end;
|
||||
if FMode = MODE_FMRAW then
|
||||
begin
|
||||
if FFilter > FILT_RAW_COUNT - 1 then FFilter := FILT_RAW_DEF;
|
||||
FFilterBW := FILT_RAW_BW[FFilter];
|
||||
end;
|
||||
if FMode in [MODE_FM, MODE_DMR, MODE_FMRAW] then ApplyFilterSlot(FFilter);
|
||||
end;
|
||||
|
||||
function TRadioController.MakeBandSettings: TBandSettings;
|
||||
@@ -3544,9 +3545,10 @@ begin
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetMode(FMode);
|
||||
|
||||
// --- Фильтр (санитизация FM до применения к DSP — как в оригинале) ---
|
||||
FFilter := B.FilterIdx;
|
||||
FFilterBW := B.FilterBW;
|
||||
SanitizeFilterForMode;
|
||||
// Банд помнит ТОЛЬКО выбранный слот; кромки живут в таблице фильтров
|
||||
// (по режиму, как в Thetis). Иначе у VAR было бы два источника истины —
|
||||
// ровно та болезнь, от которой уходили.
|
||||
ApplyFilterSlot(B.FilterIdx);
|
||||
ApplyModeFilter;
|
||||
|
||||
// --- AGC (единый маппинг — исправляет каст-баг RestoreBand) ---
|
||||
@@ -3985,72 +3987,107 @@ end;
|
||||
|
||||
procedure TRadioController.SetFilterIdx(Idx: Integer);
|
||||
begin
|
||||
// Клампим индекс под текущий режим (как FilterBWFor) — публичная команда,
|
||||
// Idx может прийти из CAT/web без проверки; FM/WFM-таблицы короче остальных.
|
||||
case FMode of
|
||||
MODE_FM: Idx := EnsureRange(Idx, 0, FILT_FM_COUNT - 1);
|
||||
MODE_WFM: Idx := EnsureRange(Idx, 0, FILT_WFM_COUNT - 1);
|
||||
MODE_DMR: Idx := 0;
|
||||
MODE_FMRAW: Idx := EnsureRange(Idx, 0, FILT_RAW_COUNT - 1);
|
||||
else Idx := EnsureRange(Idx, 0, FILT_COUNT - 1);
|
||||
// Публичная команда: Idx может прийти из CAT/web без проверки. Клампится
|
||||
// внутри ApplyFilterSlot (у FM/WFM/FMRAW пресетов меньше; VAR всегда есть).
|
||||
ApplyFilterSlot(Idx);
|
||||
CacheFilterToBand;
|
||||
ApplyModeFilter;
|
||||
Changed(rfFilter);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CacheFilterToBand;
|
||||
begin
|
||||
FBandCache[FCurrentBand].FilterIdx := FFilter;
|
||||
FBandCache[FCurrentBand].FilterBW := FFilterBW; // легаси-поле, для старых конфигов
|
||||
end;
|
||||
|
||||
procedure TRadioController.SelectFilterSlotState(Idx: Integer);
|
||||
begin
|
||||
ApplyFilterSlot(Idx);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SanitizeFilterState;
|
||||
begin
|
||||
SanitizeFilterForMode;
|
||||
end;
|
||||
|
||||
procedure TRadioController.TXFilterEdges(out Lo, Hi: Integer);
|
||||
begin
|
||||
Lo := 0; Hi := 0;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.TXFilterEdgesHz(Lo, Hi);
|
||||
end;
|
||||
|
||||
function TRadioController.FilterSlot(Mode, Idx: Integer): TFilterSlot;
|
||||
begin
|
||||
Result := FFilterTable[EnsureRange(Mode, 0, CFG_MODE_MAX)]
|
||||
[EnsureRange(Idx, 0, FILT_SLOTS - 1)];
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetFilterSlot(Mode, Idx: Integer; const S: TFilterSlot);
|
||||
begin
|
||||
Mode := EnsureRange(Mode, 0, CFG_MODE_MAX);
|
||||
Idx := EnsureRange(Idx, 0, FILT_SLOTS - 1);
|
||||
if S.Hi <= S.Lo then Exit; // пустая полоса = глухой приём
|
||||
FFilterTable[Mode][Idx] := S;
|
||||
if Assigned(FSettings) then FSettings.SaveFilters(FDevMAC, FFilterTable);
|
||||
// Правка активного слота слышна сразу — форма настроек в EWSDR мгновенная.
|
||||
if (Mode = FMode) and (Idx = FFilter) then
|
||||
begin
|
||||
ApplyFilterSlot(Idx);
|
||||
CacheFilterToBand;
|
||||
ApplyModeFilter;
|
||||
Changed(rfFilter);
|
||||
end;
|
||||
FFilter := Idx;
|
||||
case FMode of
|
||||
MODE_LSB, MODE_USB: FFilterBW := FILT_SSB_BW[Idx];
|
||||
MODE_DIGU, MODE_DIGL: FFilterBW := FILT_DIGI_BW[Idx];
|
||||
MODE_DSB: FFilterBW := FILT_DSB_BW[Idx];
|
||||
MODE_CWL, MODE_CWU: FFilterBW := FILT_CW_BW[Idx];
|
||||
MODE_WFM: FFilterBW := FILT_WFM_BW[Idx];
|
||||
MODE_DMR: FFilterBW := FILT_DMR_BW;
|
||||
MODE_FMRAW: FFilterBW := FILT_RAW_BW[Idx];
|
||||
MODE_FM:
|
||||
begin
|
||||
FFilterBW := FILT_FM_BW[Idx];
|
||||
FFMDeviation := FILT_FM_DEV[Idx];
|
||||
end;
|
||||
else FFilterBW := FILT_AM_BW[Idx];
|
||||
end;
|
||||
FBandCache[FCurrentBand].FilterBW := FFilterBW;
|
||||
end;
|
||||
|
||||
procedure TRadioController.ResetFilterSlot(Mode, Idx: Integer);
|
||||
var Def: TFilterSet;
|
||||
begin
|
||||
Mode := EnsureRange(Mode, 0, CFG_MODE_MAX);
|
||||
Def := DefaultFilterSet(Mode);
|
||||
SetFilterSlot(Mode, Idx, Def[EnsureRange(Idx, 0, FILT_SLOTS - 1)]);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ResetFilterSet(Mode: Integer);
|
||||
var i: Integer;
|
||||
begin
|
||||
for i := 0 to FILT_SLOTS - 1 do ResetFilterSlot(Mode, i);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetFilterEdges(LoHz, HiHz: Integer);
|
||||
// Drag на спектре / CAT ZZFL-ZZFH / web: пресеты не портим — уходим в VAR1.
|
||||
var S: TFilterSlot; Target: Integer;
|
||||
begin
|
||||
if HiHz - LoHz < FILT_MIN_WIDTH then Exit;
|
||||
if (FFilter = FILT_VAR1) or (FFilter = FILT_VAR2) then Target := FFilter
|
||||
else Target := FILT_VAR1;
|
||||
S := FFilterTable[FMode][Target];
|
||||
S.Lo := LoHz;
|
||||
S.Hi := HiHz;
|
||||
FFilterTable[FMode][Target] := S;
|
||||
if Assigned(FSettings) then FSettings.SaveFilters(FDevMAC, FFilterTable);
|
||||
ApplyFilterSlot(Target);
|
||||
CacheFilterToBand;
|
||||
ApplyModeFilter;
|
||||
Changed(rfFilter);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetFilterBW(BWHz: Integer);
|
||||
// Непрерывная полоса (web-ползунок): применяем запрошенный BW как есть, а
|
||||
// FFilter подбираем ближайшим пресетом — только для подсветки кнопки.
|
||||
// Непрерывная полоса (web-ползунок). Точное совпадение с пресетом выбирает
|
||||
// пресет, произвольная ширина уходит в VAR — как и перетаскивание края мышью.
|
||||
var
|
||||
BW_arr: array[0..FILT_COUNT-1] of Integer;
|
||||
best, dist, i: Integer;
|
||||
Lo, Hi, i, n: Integer;
|
||||
begin
|
||||
if FMode = MODE_DMR then BWHz := FILT_DMR_BW;
|
||||
FFilterBW := BWHz;
|
||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := MaxInt;
|
||||
case FMode of
|
||||
MODE_LSB, MODE_USB:
|
||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_SSB_BW[i];
|
||||
MODE_DIGU, MODE_DIGL:
|
||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DIGI_BW[i];
|
||||
MODE_DSB: for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_DSB_BW[i];
|
||||
MODE_CWL, MODE_CWU:
|
||||
for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_CW_BW[i];
|
||||
MODE_FM: for i := 0 to FILT_FM_COUNT-1 do BW_arr[i] := FILT_FM_BW[i];
|
||||
MODE_WFM: for i := 0 to FILT_WFM_COUNT-1 do BW_arr[i] := FILT_WFM_BW[i];
|
||||
MODE_DMR: BW_arr[0] := FILT_DMR_BW;
|
||||
MODE_FMRAW: for i := 0 to FILT_RAW_COUNT-1 do BW_arr[i] := FILT_RAW_BW[i];
|
||||
else for i := 0 to FILT_COUNT-1 do BW_arr[i] := FILT_AM_BW[i];
|
||||
end;
|
||||
best := 0; dist := MaxInt;
|
||||
for i := 0 to FILT_COUNT-1 do
|
||||
if Abs(BW_arr[i] - BWHz) < dist then
|
||||
FilterEdgesFromBW(FMode, BWHz, Lo, Hi);
|
||||
n := FilterPresetCount(FMode);
|
||||
for i := 0 to n - 1 do
|
||||
if (FFilterTable[FMode][i].Lo = Lo) and (FFilterTable[FMode][i].Hi = Hi) then
|
||||
begin
|
||||
dist := Abs(BW_arr[i] - BWHz);
|
||||
best := i;
|
||||
SetFilterIdx(i);
|
||||
Exit;
|
||||
end;
|
||||
FFilter := best;
|
||||
if FMode = MODE_FM then FFMDeviation := FILT_FM_DEV[best];
|
||||
FBandCache[FCurrentBand].FilterBW := FFilterBW;
|
||||
ApplyModeFilter;
|
||||
Changed(rfFilter);
|
||||
SetFilterEdges(Lo, Hi);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetRawFMDeviation(Hz: Integer);
|
||||
@@ -5788,6 +5825,8 @@ begin
|
||||
FNetwork.SetAlexConfig(FAlexSettings);
|
||||
FSettings.LoadOC(FDevMAC, FOCSettings);
|
||||
FNetwork.SetOCConfig(FOCSettings); // no-op на Pluto (base-класс)
|
||||
FSettings.LoadFilters(FDevMAC, FFilterTable);
|
||||
ApplyFilterSlot(FFilter); // кромки под таблицу этого устройства
|
||||
FSettings.LoadXvtr(FDevMAC, FXvtrSettings);
|
||||
FCurrentXvtr := -1;
|
||||
ApplyXvtrToNetwork;
|
||||
@@ -5959,6 +5998,7 @@ begin
|
||||
FSettings.SaveAlex(FDevMAC, FAlexSettings);
|
||||
FSettings.SaveAnt936x(FDevMAC, FAnt936x);
|
||||
FSettings.SaveOC(FDevMAC, FOCSettings);
|
||||
FSettings.SaveFilters(FDevMAC, FFilterTable);
|
||||
// XVTR LastFreq → актуальная видимая частота в момент остановки.
|
||||
if (FCurrentXvtr >= 0) and (FCurrentXvtr < CFG_XVTR_COUNT) then
|
||||
FXvtrSettings.Entries[FCurrentXvtr].LastFreq := FVfoA;
|
||||
|
||||
Reference in New Issue
Block a user