mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(slices): multi-slice receivers (FlexRadio-style) — soft fan-out + per-slice flag/audio
Independent RX slices sharing one panadapter: each has its own freq/mode/filter/AGC/NR-NB-SNB-ANF, S-meter, volume and sound card. VFO A = main receiver; B+ are soft fan-out WDSP channels within the captured span (ideal for Pluto/QO-100; HW DDC path is Stage 3). - WDSPEngine: soft fan-out (TSliceChan, ProcessSlices in DSP thread, per-slice NCO/mode/filter/AGC/NR/NB/SNB/ANF + S-meter, OnSliceAudio), FSliceLock serialises add/remove/set vs DSP; reopened on rate change. - RadioController: TCtrlSlice with its own TAudioOutput/device, Add/Remove/SetSlice*, RefreshSliceShifts keeps slices on target Hz across centre changes. Slice letters start at 'B' (A = main receiver). - UI: per-slice flags (VfoOverlay) with letter badge/close, filter-band markers in BOTH GL and CPU renderers, drag-retune, wheel; flags placed beside the filter band with flip, like the main flag. Ctrl+click adds. - Main flag (slice A) visible whenever the left panel is hidden OR any slice exists; synced with panel-driven DSP/mode/AGC changes. - Perf: S-meter decoupled from flag chrome cache (FMeterDirty + meter-only RefreshMeter via EnsureRendered); InvalidateVfoOverlay avoids global overlay-cache churn on meter ticks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+325
-11
@@ -122,6 +122,24 @@ type
|
||||
PLLLock: Boolean;
|
||||
end;
|
||||
|
||||
// Слайс уровня контроллера: логическая обёртка над WDSP-слайс-каналом
|
||||
// (в TWDSPEngine) + собственный аудио-выход со своей звуковухой.
|
||||
TCtrlSlice = record
|
||||
Active: Boolean;
|
||||
Id: Integer; // = id слайса в TWDSPEngine
|
||||
Letter: Char; // буква флага ('A'..'F'), пока для UI
|
||||
TargetHz: Double; // абсолютная частота слайса
|
||||
Mode: Integer;
|
||||
FilterLo: Integer;
|
||||
FilterHi: Integer;
|
||||
AGC: TWDSPAGCMode;
|
||||
Volume: Double; // 0..1
|
||||
Muted: Boolean;
|
||||
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
||||
DevName: string;
|
||||
Audio: TAudioOutput; // владеет; свой поток вывода
|
||||
end;
|
||||
|
||||
{ TRadioController }
|
||||
TRadioController = class
|
||||
private
|
||||
@@ -134,6 +152,11 @@ type
|
||||
FOnSpectrumData: TPixelDataEvent; // сырьё спектра наружу (UI рисует FSpecView)
|
||||
FOnWaterfallData: TPixelDataEvent; // сырьё водопада наружу (UI: децимация+рендер)
|
||||
procedure Changed(Field: TRadioField);
|
||||
// --- Мультислайсы ---
|
||||
function FindSliceIndex(Id: Integer): Integer; // -1 если нет
|
||||
procedure OnSliceAudioReady(SliceId: Integer;
|
||||
const Left, Right: array of Single; Count: Integer);
|
||||
procedure RefreshSliceShifts; // пересчёт ShiftHz всех слайсов при смене центра
|
||||
procedure ApplyAGCToEngine; // применяет FAGCMode к WDSP (единый маппинг)
|
||||
procedure ApplyModeDefaults; // дефолтный фильтр/девиация под текущий FMode
|
||||
procedure SanitizeFilterForMode; // FM: индекс фильтра 0/1 + BW/девиация (state)
|
||||
@@ -163,6 +186,12 @@ type
|
||||
FDSPEngine: TWDSPEngine;
|
||||
FAudioOut: TAudioOutput;
|
||||
FAudioIn: TAudioInput;
|
||||
|
||||
// ---- Мультислайсы (софт-fan-out) ----
|
||||
// Слайс = доп. приёмник (свой WDSP-канал в движке) + своя звуковуха.
|
||||
// TargetHz абсолютна; ShiftHz в движок = TargetHz - FCenterFreq.
|
||||
FSlices: array[0..MAX_SLICES-1] of TCtrlSlice;
|
||||
FNextSliceId: Integer; // монотонный генератор id слайсов
|
||||
FSettings: TSettingsManager;
|
||||
FChannelStore: TChannelStore;
|
||||
FDeviceStore: TDeviceStore; // общий список устройств (saved+discovered)
|
||||
@@ -467,6 +496,32 @@ type
|
||||
procedure DriveBy(Delta: Integer);
|
||||
procedure SetAtten(Idx: Integer);
|
||||
|
||||
// ---- Мультислайсы (софт-fan-out) ----
|
||||
// Создаёт слайс на TargetHz: заводит WDSP-канал в движке + свой аудио-выход
|
||||
// на устройстве DevIndex (-1=default). Возвращает Id слайса или -1 при отказе
|
||||
// (нет слотов / TargetHz вне захваченной полосы). DevName для персиста/UI.
|
||||
function AddSlice(TargetHz: Double; Mode, FilterLo, FilterHi: Integer;
|
||||
AGC: TWDSPAGCMode; Vol: Double;
|
||||
DevIndex: Integer; const DevName: string): Integer;
|
||||
procedure RemoveSlice(Id: Integer);
|
||||
procedure SetSliceTarget(Id: Integer; TargetHz: Double);
|
||||
procedure SetSliceMode(Id, Mode: Integer);
|
||||
procedure SetSliceFilter(Id, Low, High: Integer);
|
||||
procedure SetSliceModeBW(Id, Mode, BW: Integer); // режим+полоса → кромки фильтра
|
||||
class procedure FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
class function AGCModeFromUI(M: Integer): TWDSPAGCMode; // UI-индекс 0..4 → WDSP enum
|
||||
procedure SetSliceAGCMode(Id: Integer; AGC: TWDSPAGCMode);
|
||||
procedure SetSliceVolume(Id: Integer; Vol: Double); // 0..1
|
||||
procedure SetSliceMute(Id: Integer; Mute: Boolean);
|
||||
procedure SetSliceDSP(Id, NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
||||
function SliceSMeter(Id: Integer): Double; // dBm, из DSP-потока
|
||||
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
||||
function SliceCount: Integer;
|
||||
function GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
||||
// Диапазон захваченной полосы вокруг центра, куда можно ставить софт-слайс.
|
||||
procedure CapturedSpan(out LoHz, HiHz: Double);
|
||||
function SliceFitsCapture(TargetHz: Double): Boolean;
|
||||
|
||||
// Pluto TX-мощность через аттенюацию: drive% → дБ (потолок FPlutoTxMaxAttDb на
|
||||
// 100%, выкл на приёме). Логика/маппинг здесь; бэкенд лишь пишет hardwaregain.
|
||||
function PlutoTxAttForDrive: Double; // дБ<0 под текущий FDrivePercent
|
||||
@@ -625,6 +680,8 @@ begin
|
||||
FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX)
|
||||
FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0;
|
||||
FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000;
|
||||
FNextSliceId := 1; // мультислайсы: генератор id
|
||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||
FZoomFactor := 0.0; FPanSlider := 0.5; // 0.0 = весь span (без зума)
|
||||
FCurrentBand := 5; FCurrentXvtr := -1;
|
||||
FSpectrumBufCount := 1024; FWaterfallBufCount := 1024;
|
||||
@@ -649,6 +706,9 @@ begin
|
||||
// BufSize=512: латентность 512/48000 ≈ 10.7мс — допустимо для SDR; меньший
|
||||
// буфер вдвое сокращает построение downsampler в OpenChannel RXA.
|
||||
FDSPEngine := TWDSPEngine.Create(ASampleRate, 48000, 512);
|
||||
// Роутинг аудио слайсов на их звуковухи — внутренний, вешаем сразу (не
|
||||
// зависит от фронтенда, в отличие от OnAudio/OnSpectrum).
|
||||
FDSPEngine.OnSliceAudio := OnSliceAudioReady;
|
||||
|
||||
// QO-100 beacon lock: эталон частоты = декодер (см. ServiceBeaconLock).
|
||||
FBeaconRefHz := 10489750000.0; // центральный BPSK-маяк NB-транспондера (downlink)
|
||||
@@ -672,12 +732,21 @@ begin
|
||||
end;
|
||||
|
||||
procedure TRadioController.FreeEngines;
|
||||
var i: Integer;
|
||||
begin
|
||||
if Assigned(FNetwork) then
|
||||
begin
|
||||
if FNetwork.Connected then FNetwork.Disconnect;
|
||||
FreeAndNil(FNetwork);
|
||||
end;
|
||||
// Слайсы: закрываем их аудио-выходы (WDSP-каналы закроет FDSPEngine.Close ниже).
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].Audio <> nil) then
|
||||
begin
|
||||
FSlices[i].Audio.Close;
|
||||
FreeAndNil(FSlices[i].Audio);
|
||||
FSlices[i].Active := False;
|
||||
end;
|
||||
if Assigned(FAudioOut) then begin FAudioOut.Close; FreeAndNil(FAudioOut); end;
|
||||
if Assigned(FAudioIn) then begin FAudioIn.Close; FreeAndNil(FAudioIn); end;
|
||||
if Assigned(FDSPEngine) then
|
||||
@@ -966,6 +1035,227 @@ begin
|
||||
FAudioOut.Write(Left, Right, Count);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Мультислайсы (софт-fan-out)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function TRadioController.FindSliceIndex(Id: Integer): Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := -1;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (FSlices[i].Id = Id) then Exit(i);
|
||||
end;
|
||||
|
||||
// DSP-поток: аудио готового слайса → его собственный выход. Слайс — локальный
|
||||
// монитор (в web/на радио не уходит), поэтому только на свою звуковуху.
|
||||
procedure TRadioController.OnSliceAudioReady(SliceId: Integer;
|
||||
const Left, Right: array of Single; Count: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
if not FLocalAudio then Exit;
|
||||
idx := FindSliceIndex(SliceId);
|
||||
if idx < 0 then Exit;
|
||||
if FSlices[idx].Audio <> nil then
|
||||
FSlices[idx].Audio.Write(Left, Right, Count);
|
||||
end;
|
||||
|
||||
procedure TRadioController.RefreshSliceShifts;
|
||||
var i: Integer;
|
||||
begin
|
||||
if not Assigned(FDSPEngine) then Exit;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active then
|
||||
FDSPEngine.SetSliceShift(FSlices[i].Id, FSlices[i].TargetHz - FCenterFreq);
|
||||
end;
|
||||
|
||||
procedure TRadioController.CapturedSpan(out LoHz, HiHz: Double);
|
||||
// Полезная полоса ±47.5% от захвата (запас на краях фильтра/анти-алиасинга).
|
||||
var Half: Double;
|
||||
begin
|
||||
Half := FSampleRate * 0.475;
|
||||
LoHz := FCenterFreq - Half;
|
||||
HiHz := FCenterFreq + Half;
|
||||
end;
|
||||
|
||||
function TRadioController.SliceFitsCapture(TargetHz: Double): Boolean;
|
||||
var Lo, Hi: Double;
|
||||
begin
|
||||
CapturedSpan(Lo, Hi);
|
||||
Result := (TargetHz >= Lo) and (TargetHz <= Hi);
|
||||
end;
|
||||
|
||||
function TRadioController.AddSlice(TargetHz: Double;
|
||||
Mode, FilterLo, FilterHi: Integer; AGC: TWDSPAGCMode; Vol: Double;
|
||||
DevIndex: Integer; const DevName: string): Integer;
|
||||
var
|
||||
i, slot, Id: Integer;
|
||||
AO: TAudioOutput;
|
||||
begin
|
||||
Result := -1;
|
||||
if not Assigned(FDSPEngine) then Exit;
|
||||
if not SliceFitsCapture(TargetHz) then Exit; // вне захваченной полосы (Этап 3=HW DDC)
|
||||
|
||||
slot := -1;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if not FSlices[i].Active then begin slot := i; Break; end;
|
||||
if slot < 0 then Exit; // нет свободных слотов
|
||||
|
||||
Id := FNextSliceId;
|
||||
if not FDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi,
|
||||
TargetHz - FCenterFreq, Vol, AGC) then Exit;
|
||||
Inc(FNextSliceId);
|
||||
|
||||
// Свой аудио-выход слайса на выбранной звуковухе.
|
||||
AO := TAudioOutput.Create(48000);
|
||||
AO.OutputBufferSize := FSettings.LoadAudioBufferSize;
|
||||
AO.DeviceIndex := DevIndex;
|
||||
if FLocalAudio then AO.Open;
|
||||
|
||||
FSlices[slot].Active := True;
|
||||
FSlices[slot].Id := Id;
|
||||
// Буква флага. Слайс A — это главный приёмник (не в FSlices[]), поэтому доп.
|
||||
// слайсы нумеруются с 'B' (slot 0 → B, 1 → C, …), иначе получалось два «A».
|
||||
FSlices[slot].Letter := Chr(Ord('B') + slot);
|
||||
FSlices[slot].TargetHz := TargetHz;
|
||||
FSlices[slot].Mode := Mode;
|
||||
FSlices[slot].FilterLo := FilterLo;
|
||||
FSlices[slot].FilterHi := FilterHi;
|
||||
FSlices[slot].AGC := AGC;
|
||||
FSlices[slot].Volume := Vol;
|
||||
FSlices[slot].Muted := False;
|
||||
FSlices[slot].DeviceIndex := DevIndex;
|
||||
FSlices[slot].DevName := DevName;
|
||||
FSlices[slot].Audio := AO;
|
||||
|
||||
Result := Id;
|
||||
Changed(rfDevice);
|
||||
end;
|
||||
|
||||
procedure TRadioController.RemoveSlice(Id: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.RemoveSlice(Id);
|
||||
if FSlices[idx].Audio <> nil then
|
||||
begin
|
||||
FSlices[idx].Audio.Close;
|
||||
FreeAndNil(FSlices[idx].Audio);
|
||||
end;
|
||||
FSlices[idx].Active := False;
|
||||
Changed(rfDevice);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceTarget(Id: Integer; TargetHz: Double);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].TargetHz := TargetHz;
|
||||
if Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetSliceShift(Id, TargetHz - FCenterFreq);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceMode(Id, Mode: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].Mode := Mode;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMode(Id, Mode);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].FilterLo := Low;
|
||||
FSlices[idx].FilterHi := High;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceFilter(Id, Low, High);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceAGCMode(Id: Integer; AGC: TWDSPAGCMode);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].AGC := AGC;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceAGC(Id, AGC);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceVolume(Id: Integer; Vol: Double);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].Volume := Vol;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceVolume(Id, Vol);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceMute(Id: Integer; Mute: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].Muted := Mute;
|
||||
if Assigned(FDSPEngine) then FDSPEngine.SetSliceMute(Id, Mute);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceDSP(Id, NRMode, NBMode: Integer;
|
||||
SNBOn, ANFOn: Boolean);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
if Assigned(FDSPEngine) then
|
||||
begin
|
||||
FDSPEngine.SetSliceNRMode(Id, NRMode);
|
||||
FDSPEngine.SetSliceNBMode(Id, NBMode);
|
||||
FDSPEngine.SetSliceSNB(Id, SNBOn);
|
||||
FDSPEngine.SetSliceANF(Id, ANFOn);
|
||||
end;
|
||||
end;
|
||||
|
||||
function TRadioController.SliceSMeter(Id: Integer): Double;
|
||||
begin
|
||||
if Assigned(FDSPEngine) then Result := FDSPEngine.GetSliceSMeter(Id)
|
||||
else Result := -130.0;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceDevice(Id, DevIndex: Integer;
|
||||
const DevName: string);
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSlices[idx].DeviceIndex := DevIndex;
|
||||
FSlices[idx].DevName := DevName;
|
||||
if FSlices[idx].Audio <> nil then
|
||||
begin
|
||||
FSlices[idx].Audio.Close;
|
||||
FSlices[idx].Audio.DeviceIndex := DevIndex;
|
||||
if FLocalAudio then FSlices[idx].Audio.Open;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TRadioController.SliceCount: Integer;
|
||||
var i: Integer;
|
||||
begin
|
||||
Result := 0;
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active then Inc(Result);
|
||||
end;
|
||||
|
||||
function TRadioController.GetSlice(Id: Integer; out S: TCtrlSlice): Boolean;
|
||||
var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
Result := idx >= 0;
|
||||
if Result then S := FSlices[idx];
|
||||
end;
|
||||
|
||||
procedure TRadioController.OnSpectrumReady(const Pixels: array of Single;
|
||||
Count: Integer);
|
||||
// DSP-поток: сохраняем последний кадр для web-зеркала и отдаём сырьё наружу
|
||||
@@ -1335,16 +1625,31 @@ begin
|
||||
Result := Assigned(FBeaconDec) and FBeaconDec.GetLastFrame(Frame, RSErrors);
|
||||
end;
|
||||
|
||||
// Кромки полосы под режим/полосу (единый расчёт для главного канала и слайсов).
|
||||
class procedure TRadioController.FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
var Half: Integer;
|
||||
begin
|
||||
Half := BW div 2;
|
||||
case Mode of
|
||||
0: begin Lo := -BW; Hi := -100; end; // LSB
|
||||
1: begin Lo := 100; Hi := BW; end; // USB
|
||||
else Lo := -Half; Hi := Half; // DSB/CW/FM/AM/SAM
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceModeBW(Id, Mode, BW: Integer);
|
||||
var Lo, Hi: Integer;
|
||||
begin
|
||||
FilterEdgesFor(Mode, BW, Lo, Hi);
|
||||
SetSliceMode(Id, Mode);
|
||||
SetSliceFilter(Id, Lo, Hi);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyModeFilter;
|
||||
var
|
||||
Lo, Hi, Half: Integer;
|
||||
Lo, Hi: Integer;
|
||||
begin
|
||||
Half := FFilterBW div 2;
|
||||
case FMode of
|
||||
0: begin Lo := -FFilterBW; Hi := -100; end; // LSB
|
||||
1: begin Lo := 100; Hi := FFilterBW; end; // USB
|
||||
else Lo := -Half; Hi := Half; // DSB/CW/FM/AM/SAM
|
||||
end;
|
||||
FilterEdgesFor(FMode, FFilterBW, Lo, Hi);
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
begin
|
||||
FDSPEngine.SetFilter(Lo, Hi);
|
||||
@@ -1921,6 +2226,7 @@ begin
|
||||
NeedPush := True;
|
||||
end;
|
||||
|
||||
RefreshSliceShifts; // центр мог сместиться — держим слайсы на их TargetHz
|
||||
if NeedPush then PushNetworkState;
|
||||
Changed(rfActiveVfo);
|
||||
end;
|
||||
@@ -1941,6 +2247,7 @@ begin
|
||||
FCenterFreq := Hz;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
FDSPEngine.SetShift(ActiveVfoHz - FCenterFreq);
|
||||
RefreshSliceShifts; // слайсы привязаны к абсолютной частоте — сдвинуть NCO
|
||||
PushNetworkState;
|
||||
Changed(rfCenterFreq);
|
||||
end;
|
||||
@@ -1972,6 +2279,7 @@ begin
|
||||
end;
|
||||
FCenterFreq := ActiveFreq;
|
||||
if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetShift(0.0);
|
||||
RefreshSliceShifts;
|
||||
PushNetworkState;
|
||||
Changed(rfVfoA);
|
||||
Changed(rfVfoB);
|
||||
@@ -2057,14 +2365,20 @@ begin
|
||||
Changed(rfFilter);
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyAGCToEngine;
|
||||
// UI-индекс 0..4 (FAST/MED/SLOW/LONG/OFF) -> WDSP enum. Единый маппинг для всех
|
||||
// фронтендов и слайсов (раньше web/слайдер ошибочно кастовали индекс прямо в enum).
|
||||
class function TRadioController.AGCModeFromUI(M: Integer): TWDSPAGCMode;
|
||||
const
|
||||
// UI-индекс 0..4 (FAST/MED/SLOW/LONG/OFF) -> WDSP enum. Единый маппинг для
|
||||
// всех фронтендов (раньше web/слайдер ошибочно кастовали индекс прямо в enum).
|
||||
AGCModes: array[0..4] of TWDSPAGCMode = (agcFast, agcMedium, agcSlow, agcLong, agcOff);
|
||||
begin
|
||||
if (M < 0) or (M > 4) then M := 1;
|
||||
Result := AGCModes[M];
|
||||
end;
|
||||
|
||||
procedure TRadioController.ApplyAGCToEngine;
|
||||
begin
|
||||
if FWDSPReady and Assigned(FDSPEngine) and (FAGCMode >= 0) and (FAGCMode <= 4) then
|
||||
FDSPEngine.SetAGC(AGCModes[FAGCMode], 50.0);
|
||||
FDSPEngine.SetAGC(AGCModeFromUI(FAGCMode), 50.0);
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetAGCMode(M: Integer);
|
||||
|
||||
Reference in New Issue
Block a user