mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
WIP: TX implementation — per-device settings, separate analyzer, sound-card mic
- Settings: TTXSettings record + LoadTX/SaveTX, JSON section "tx" per MAC - WDSPEngine: separate TX_DISP_ID analyzer with own FFT/Window/Det/Avg parameters, ApplyTXAnalyzerSettings + setters - WDSPEngine: full TX chain setters (Filter/Compressor/Leveler/ALC/ PhaseRot/EQ/AMCarrier/FM/CTCSS) + ApplyTXChainSettings - WDSPEngine: ProcessTXBlock now feeds TX analyzer in DISPLAY_BLOCK_SIZE chunks (was passing whole FTXOut → analyzer ignored most of it) - WDSPEngine: ActiveDisplayID switches RX/TX in UpdateSpectrum - WDSPEngine: RX audio muted while TX active to avoid local feedback - WDSPEngine: default EQ frequencies in Create (zero freqs broke TX chain) - WDSPEngine: PushTXMicSampleD + OnPullMicSamples for sound-card path - AudioInput: Available counter for non-blocking pull - MainForm: PullSoundCardMic, BuildMicLineSelectByte, SendDUCSpecific with mic bias/boost/line/PTT bits, OnTXSettingsChange - MainForm: auto-switch MicSource on TX device select, TX diagnostic in StatusBar panel [5] - SettingsForm: new Transmit nav page with Microphone, Filter, Dynamic processing, EQ, AM/FM/CTCSS, TX Display sections NOT YET WORKING: sound-card mic produces ring activity but signal still not visible on TX spectrum/waterfall — needs further investigation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+533
-73
@@ -95,7 +95,8 @@ const
|
||||
RXA_CHAN = 0;
|
||||
TXA_CHAN = 1; // используем разные каналы для RX и TX
|
||||
|
||||
DISP_ID = 0; // ID для XCreateAnalyzer
|
||||
RX_DISP_ID = 0; // analyzer для RX-спектра/водопада
|
||||
TX_DISP_ID = 1; // analyzer для TX-спектра/водопада
|
||||
DSP_BUFSIZE = 1024;
|
||||
SPECTRUM_PIXELS = 1024;
|
||||
DISPLAY_BLOCK_SIZE = 64;
|
||||
@@ -127,6 +128,13 @@ type
|
||||
TOnTXIQReady = procedure(const Buf: array of Double; Count: Integer) of object;
|
||||
TOnWaterfallReady = procedure(const Pixels: array of Single;
|
||||
Count: Integer) of object;
|
||||
// Sound-card mic source: TTXDSPThread дёргает этот колбэк перед каждым тиком,
|
||||
// чтобы вытянуть накопленные сэмплы из внешнего буфера (например PortAudio).
|
||||
// Реализация должна вернуть кол-во отправленных в WDSP сэмплов.
|
||||
TPullMicSamplesFunc = function(Max: Integer): Integer of object;
|
||||
|
||||
// TX mic source: откуда получаем сэмплы для модулятора.
|
||||
TTXMicSource = (txmsRadio, txmsSoundCard);
|
||||
|
||||
{ TWDSPEngine }
|
||||
TWDSPEngine = class
|
||||
@@ -154,6 +162,10 @@ type
|
||||
// Отдельный мелкий feed для display-анализатора, как в Thetis spec_blocksize.
|
||||
FDispBuf: array of Double;
|
||||
FDispPos: Integer;
|
||||
// То же самое для TX-analyzer: TX_DISP_ID получает сэмплы чанками
|
||||
// по DISPLAY_BLOCK_SIZE из FTXOut в ProcessTXBlock.
|
||||
FTXDispBuf: array of Double;
|
||||
FTXDispPos: Integer;
|
||||
// Аудио выходные буферы — аллоцируем один раз
|
||||
FOutL: array of Single;
|
||||
FOutR: array of Single;
|
||||
@@ -196,6 +208,50 @@ type
|
||||
FTXMicSem: PRTLEvent; // сигнал: есть новые mic сэмплы
|
||||
FTXThread: TThread;
|
||||
FTXActive: Boolean;
|
||||
FActiveDisplayID: Integer; // RX_DISP_ID или TX_DISP_ID
|
||||
FTXMicSource: TTXMicSource;
|
||||
FOnPullMic: TPullMicSamplesFunc;
|
||||
|
||||
// Кэшированные TX-параметры — применяются после OpenChannel(TXA) и при изменении.
|
||||
// Для Filter/Compressor/Leveler/ALC/PhaseRot/EQ/AM/FM/CTCSS.
|
||||
FTXFilterLow: Integer;
|
||||
FTXFilterHigh: Integer;
|
||||
FTXFilterNC: Integer;
|
||||
FTXFilterMP: Integer;
|
||||
FTXFilterWindow: Integer;
|
||||
FTXMicGainDB: Double;
|
||||
FTXCompressorOn: Boolean;
|
||||
FTXCompressorGain:Double;
|
||||
FTXLevelerOn: Boolean;
|
||||
FTXLevelerTop: Double;
|
||||
FTXLevelerDecay: Integer;
|
||||
FTXALCOn: Boolean;
|
||||
FTXALCMaxGain: Double;
|
||||
FTXALCDecay: Integer;
|
||||
FTXPhaseRotOn: Boolean;
|
||||
FTXPhaseRotStages:Integer;
|
||||
FTXPhaseRotFreq: Double;
|
||||
FTXEQOn: Boolean;
|
||||
FTXEQNumBands: Integer;
|
||||
FTXEQGains: array[0..10] of Double;
|
||||
FTXEQFreqs: array[0..10] of Double;
|
||||
FTXAMCarrier: Double;
|
||||
FTXFMDeviation: Double;
|
||||
FTXFMLowCut: Integer;
|
||||
FTXFMHighCut: Integer;
|
||||
FTXFMEmphPos: Integer;
|
||||
FTXCTCSSOn: Boolean;
|
||||
FTXCTCSSFreq: Double;
|
||||
|
||||
// TX-display параметры (отдельные от RX) — применяются к TX_DISP_ID.
|
||||
FTXFFTSize: Integer;
|
||||
FTXWindowType: Integer;
|
||||
FTXSpecDetector: Integer;
|
||||
FTXSpecAvgMode: Integer;
|
||||
FTXSpecAvgTimeMS: Double;
|
||||
FTXWfDetector: Integer;
|
||||
FTXWfAvgMode: Integer;
|
||||
FTXWfAvgTimeMS: Double;
|
||||
|
||||
FMode: Integer;
|
||||
FSMeter: Double;
|
||||
@@ -239,6 +295,10 @@ type
|
||||
procedure ApplyANFState;
|
||||
procedure ApplyNoiseFilterState;
|
||||
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
||||
procedure FeedTXDisplaySample(const I, Q: Double);
|
||||
procedure ApplyTXChainSettings; // прокидывает все FTX* поля в WDSP
|
||||
procedure ApplyRXAnalyzerSettings; // применяет RX FFT/Window/Det/Avg к RX_DISP_ID
|
||||
procedure ApplyTXAnalyzerSettings; // применяет TX FFT/Window/Det/Avg к TX_DISP_ID
|
||||
|
||||
public
|
||||
constructor Create(SampleRate: Integer = 192000;
|
||||
@@ -283,9 +343,33 @@ type
|
||||
procedure SetMicGain(GainDB: Double);
|
||||
procedure SetTXRun(Run: Boolean);
|
||||
|
||||
// TX mic вход — вызывается из receive thread (thread-safe lock-free)
|
||||
// Src: 16-bit big-endian hardware mic samples, N — кол-во сэмплов
|
||||
// Расширенное TX управление (применяются мгновенно если канал открыт)
|
||||
procedure SetTXFilterFull(Low, High, NC, MP, Window: Integer);
|
||||
procedure SetTXCompressor(Enable: Boolean; GainDB: Double);
|
||||
procedure SetTXLeveler(Enable: Boolean; TopDB: Double; DecayMS: Integer);
|
||||
procedure SetTXALC(Enable: Boolean; MaxGainDB: Double; DecayMS: Integer);
|
||||
procedure SetTXPhaseRot(Enable: Boolean; Stages: Integer; FreqHz: Double);
|
||||
procedure SetTXEQ(Enable: Boolean; NumBands: Integer;
|
||||
const Gains, Freqs: array of Double);
|
||||
procedure SetTXAMCarrierLevel(Level: Double);
|
||||
procedure SetTXFMParams(Deviation: Double; LowCut, HighCut, EmphPos: Integer);
|
||||
procedure SetTXCTCSS(Enable: Boolean; FreqHz: Double);
|
||||
// Источник микрофона: Radio (HW mic-stream) или SoundCard (PortAudio).
|
||||
procedure SetTXMicSource(Source: TTXMicSource);
|
||||
|
||||
// TX display: отдельные FFT/window/detector/avg для TX-analyzer (TX_DISP_ID).
|
||||
// Аналог RX-сеттеров, но применяются только к TX-anaylzer.
|
||||
procedure SetTXFFTParams(FFTSize, WinType: Integer);
|
||||
procedure SetTXSpectrumDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
|
||||
procedure SetTXWaterfallDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
|
||||
|
||||
// TX mic вход — вызывается из receive thread (thread-safe lock-free).
|
||||
// Src: 16-bit big-endian hardware mic samples, N — кол-во сэмплов.
|
||||
// Если MicSource=SoundCard, эти сэмплы игнорируются.
|
||||
procedure PushTXMicSamples16(const Src: array of SmallInt; N: Integer);
|
||||
// Прямой push double-сэмпла (-1..+1), для sound-card пути.
|
||||
// Должен вызываться из ОДНОГО потока (PortAudio callback).
|
||||
procedure PushTXMicSampleD(const S: Double);
|
||||
|
||||
// Настройки анализатора (применяются немедленно если открыт)
|
||||
procedure SetFFTParams(FFTSize, WinType: Integer);
|
||||
@@ -329,6 +413,24 @@ type
|
||||
property OnWaterfall: TOnWaterfallReady read FOnWaterfall write FOnWaterfall;
|
||||
property OnTXIQ: TOnTXIQReady read FOnTXIQ write FOnTXIQ;
|
||||
property TXActive: Boolean read FTXActive;
|
||||
// Активный display ID — RX_DISP_ID или TX_DISP_ID (зависит от FTXActive).
|
||||
// SpectrumView/Waterfall должны читать пиксели именно из этого ID.
|
||||
property ActiveDisplayID: Integer read FActiveDisplayID;
|
||||
property TXMicSource: TTXMicSource read FTXMicSource;
|
||||
property OnPullMicSamples: TPullMicSamplesFunc
|
||||
read FOnPullMic write FOnPullMic;
|
||||
|
||||
// Диагностика mic ring buffer: сколько сэмплов накоплено TX-цепью.
|
||||
function TXMicRingAvail: Integer;
|
||||
|
||||
property TXFFTSize: Integer read FTXFFTSize;
|
||||
property TXWindowType: Integer read FTXWindowType;
|
||||
property TXSpecDetector: Integer read FTXSpecDetector;
|
||||
property TXSpecAvgMode: Integer read FTXSpecAvgMode;
|
||||
property TXSpecAvgTimeMS: Double read FTXSpecAvgTimeMS;
|
||||
property TXWfDetector: Integer read FTXWfDetector;
|
||||
property TXWfAvgMode: Integer read FTXWfAvgMode;
|
||||
property TXWfAvgTimeMS: Double read FTXWfAvgTimeMS;
|
||||
end;
|
||||
|
||||
implementation
|
||||
@@ -426,6 +528,13 @@ begin
|
||||
if Terminated then Break;
|
||||
if not FEngine.FTXActive then Continue;
|
||||
|
||||
// Если выбран sound-card mic — тянем сэмплы из внешнего источника
|
||||
// (PortAudio через FOnPullMic в MainForm) ПЕРЕД проверкой ring buffer.
|
||||
// Callback сам пушит сэмплы в FTXMicRing через PushTXMicSampleD.
|
||||
if (FEngine.FTXMicSource = txmsSoundCard) and
|
||||
Assigned(FEngine.FOnPullMic) then
|
||||
FEngine.FOnPullMic(FEngine.FAudioBufSize * 2);
|
||||
|
||||
// Обрабатываем накопившиеся блоки с реальными mic-данными
|
||||
repeat
|
||||
Avail := (FEngine.FTXMicHead - FEngine.FTXMicTail + TX_MIC_RING)
|
||||
@@ -571,7 +680,9 @@ begin
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
SetLength(FSpecBuf, FBufSize * 2);
|
||||
SetLength(FDispBuf, DISPLAY_BLOCK_SIZE * 2);
|
||||
SetLength(FTXDispBuf, DISPLAY_BLOCK_SIZE * 2);
|
||||
FDispPos := 0;
|
||||
FTXDispPos := 0;
|
||||
SetLength(FOutL, FAudioBufSize);
|
||||
SetLength(FOutR, FAudioBufSize);
|
||||
|
||||
@@ -584,12 +695,68 @@ begin
|
||||
FDisplayThread := nil;
|
||||
|
||||
// TX инициализация
|
||||
FTXMicHead := 0;
|
||||
FTXMicTail := 0;
|
||||
FTXActive := False;
|
||||
FTXThread := nil;
|
||||
FTXMicSem := RTLEventCreate;
|
||||
FTXMicHead := 0;
|
||||
FTXMicTail := 0;
|
||||
FTXActive := False;
|
||||
FActiveDisplayID := RX_DISP_ID;
|
||||
FTXMicSource := txmsRadio;
|
||||
FOnPullMic := nil;
|
||||
FTXThread := nil;
|
||||
FTXMicSem := RTLEventCreate;
|
||||
FillChar(FTXMicRing, SizeOf(FTXMicRing), 0);
|
||||
|
||||
// TX-цепь — дефолты, согласуются с Settings.DefaultTX
|
||||
FTXFilterLow := 100;
|
||||
FTXFilterHigh := 2800;
|
||||
FTXFilterNC := 1024;
|
||||
FTXFilterMP := 1;
|
||||
FTXFilterWindow := 1;
|
||||
FTXMicGainDB := 10.0;
|
||||
FTXCompressorOn := False;
|
||||
FTXCompressorGain:= 5.0;
|
||||
FTXLevelerOn := True;
|
||||
FTXLevelerTop := 5.0;
|
||||
FTXLevelerDecay := 500;
|
||||
FTXALCOn := True;
|
||||
FTXALCMaxGain := 0.0;
|
||||
FTXALCDecay := 10;
|
||||
FTXPhaseRotOn := False;
|
||||
FTXPhaseRotStages:= 8;
|
||||
FTXPhaseRotFreq := 338.0;
|
||||
FTXEQOn := False;
|
||||
FTXEQNumBands := 10;
|
||||
FillChar(FTXEQGains, SizeOf(FTXEQGains), 0);
|
||||
// Дефолтные центры полос EQ (Hz). Должны быть валидными ДАЖЕ при EQOn=False —
|
||||
// SetTXAEQProfile вызывается в ApplyTXChainSettings всегда, и нулевые freqs
|
||||
// могут сломать TX-фильтры внутри WDSP.
|
||||
FTXEQFreqs[0] := 0;
|
||||
FTXEQFreqs[1] := 100;
|
||||
FTXEQFreqs[2] := 200;
|
||||
FTXEQFreqs[3] := 400;
|
||||
FTXEQFreqs[4] := 700;
|
||||
FTXEQFreqs[5] := 1100;
|
||||
FTXEQFreqs[6] := 1500;
|
||||
FTXEQFreqs[7] := 2000;
|
||||
FTXEQFreqs[8] := 2500;
|
||||
FTXEQFreqs[9] := 3000;
|
||||
FTXEQFreqs[10] := 3500;
|
||||
FTXAMCarrier := 0.5;
|
||||
FTXFMDeviation := 5000.0;
|
||||
FTXFMLowCut := 300;
|
||||
FTXFMHighCut := 3000;
|
||||
FTXFMEmphPos := 1;
|
||||
FTXCTCSSOn := False;
|
||||
FTXCTCSSFreq := 100.0;
|
||||
|
||||
// TX-display defaults
|
||||
FTXFFTSize := 16384;
|
||||
FTXWindowType := 2;
|
||||
FTXSpecDetector := 0;
|
||||
FTXSpecAvgMode := 3;
|
||||
FTXSpecAvgTimeMS := 30.0;
|
||||
FTXWfDetector := 0;
|
||||
FTXWfAvgMode := 3;
|
||||
FTXWfAvgTimeMS := 120.0;
|
||||
end;
|
||||
|
||||
destructor TWDSPEngine.Destroy;
|
||||
@@ -612,8 +779,19 @@ begin
|
||||
if FAnalyzerOpen then Exit;
|
||||
|
||||
Success := -1;
|
||||
XCreateAnalyzer(DISP_ID, @Success, 262144, 1, 1, nil);
|
||||
if Success <> 0 then Exit;
|
||||
XCreateAnalyzer(RX_DISP_ID, @Success, 262144, 1, 1, nil);
|
||||
if Success <> 0 then
|
||||
begin
|
||||
FLastError := Format('XCreateAnalyzer(RX) failed: %d', [Success]);
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Отдельный analyzer для TX-спектра/водопада. Ошибку логируем в LastError,
|
||||
// но не считаем фатальной — RX без TX-display продолжает работать.
|
||||
Success := -1;
|
||||
XCreateAnalyzer(TX_DISP_ID, @Success, 262144, 1, 1, nil);
|
||||
if Success <> 0 then
|
||||
FLastError := Format('XCreateAnalyzer(TX) failed: %d', [Success]);
|
||||
|
||||
FFlp[0] := 0;
|
||||
ApplyAnalyzerSettings;
|
||||
@@ -623,53 +801,81 @@ begin
|
||||
FDisplayThread := TDisplayThread.Create(Self);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyAnalyzerSettings;
|
||||
// Вызывается из OpenAnalyzer и при изменении FFT/window/detector/avg параметров
|
||||
procedure TWDSPEngine.ApplyRXAnalyzerSettings;
|
||||
// Применяет FFFTSize / FWindowType / FSpec* / FWf* к RX_DISP_ID.
|
||||
var
|
||||
AnalyzerFFT: Integer;
|
||||
AvgCount: Integer;
|
||||
Backmult: Double;
|
||||
WaterfallAvgCount: Integer;
|
||||
WaterfallBackmult: Double;
|
||||
MaxW: Integer;
|
||||
Overlap: Integer;
|
||||
const
|
||||
KEEP_TIME_SEC = 0.10;
|
||||
var
|
||||
DisplaySampleRate: Integer;
|
||||
DisplayPixels: Integer;
|
||||
AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels: Integer;
|
||||
Backmult, WfBackmult: Double;
|
||||
const KEEP_TIME_SEC = 0.10;
|
||||
begin
|
||||
// FFT Size пользователя управляет именно display-analyzer, как в Thetis,
|
||||
// но визуальная нормализация должна идти к ширине видимого окна, как в piHPSDR.
|
||||
AnalyzerFFT := CalcDisplayFFTSize;
|
||||
FDispPos := 0;
|
||||
MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FSampleRate,
|
||||
KEEP_TIME_SEC * AnalyzerFFT * EnsureRange(FDisplayFPS, 5, 100)));
|
||||
KEEP_TIME_SEC * AnalyzerFFT * EnsureRange(FDisplayFPS, 5, 100)));
|
||||
CalcAnalyzerTiming(AnalyzerFFT, Overlap, FEffectiveDisplayFPS);
|
||||
DisplayPixels := EnsureRange(FLastSpectrumW, 64, SPECTRUM_PIXELS);
|
||||
FDisplayPixelCount := DisplayPixels;
|
||||
DisplaySampleRate := FSampleRate;
|
||||
|
||||
// Раздельные выходы display: pixout=0 панорама, pixout=1 водопад.
|
||||
SetAnalyzer(DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE, FWindowType, 14.0,
|
||||
Overlap, 0, 0.0, 0.0, DisplayPixels, 1, 0, 0.0, 0.0, MaxW);
|
||||
|
||||
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
|
||||
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WaterfallAvgCount, WaterfallBackmult);
|
||||
SetDisplayDetectorMode(DISP_ID, 0, FSpecDetector);
|
||||
SetDisplayAverageMode(DISP_ID, 0, FSpecAvgMode);
|
||||
SetDisplayNumAverage(DISP_ID, 0, AvgCount);
|
||||
SetDisplayAvBackmult(DISP_ID, 0, Backmult);
|
||||
SetDisplayNormOneHz(DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
|
||||
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, WfAvgCount, WfBackmult);
|
||||
|
||||
SetDisplayDetectorMode(DISP_ID, 1, FWfDetector);
|
||||
SetDisplayAverageMode(DISP_ID, 1, FWfAvgMode);
|
||||
SetDisplayNumAverage(DISP_ID, 1, WaterfallAvgCount);
|
||||
SetDisplayAvBackmult(DISP_ID, 1, WaterfallBackmult);
|
||||
SetDisplayNormOneHz(DISP_ID, 1, 0);
|
||||
SetAnalyzer(RX_DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE,
|
||||
FWindowType, 14.0, Overlap, 0, 0.0, 0.0, DisplayPixels,
|
||||
1, 0, 0.0, 0.0, MaxW);
|
||||
SetDisplayDetectorMode(RX_DISP_ID, 0, FSpecDetector);
|
||||
SetDisplayAverageMode (RX_DISP_ID, 0, FSpecAvgMode);
|
||||
SetDisplayNumAverage (RX_DISP_ID, 0, AvgCount);
|
||||
SetDisplayAvBackmult (RX_DISP_ID, 0, Backmult);
|
||||
SetDisplayNormOneHz (RX_DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
|
||||
SetDisplayDetectorMode(RX_DISP_ID, 1, FWfDetector);
|
||||
SetDisplayAverageMode (RX_DISP_ID, 1, FWfAvgMode);
|
||||
SetDisplayNumAverage (RX_DISP_ID, 1, WfAvgCount);
|
||||
SetDisplayAvBackmult (RX_DISP_ID, 1, WfBackmult);
|
||||
SetDisplayNormOneHz (RX_DISP_ID, 1, 0);
|
||||
SetDisplaySampleRate (RX_DISP_ID, FSampleRate);
|
||||
end;
|
||||
|
||||
// В Thetis сюда идёт реальный sample rate приёмника.
|
||||
SetDisplaySampleRate(DISP_ID, DisplaySampleRate);
|
||||
procedure TWDSPEngine.ApplyTXAnalyzerSettings;
|
||||
// Применяет FTXFFTSize / FTXWindowType / FTXSpec* / FTXWf* к TX_DISP_ID.
|
||||
// FFT/Window/Det/Avg отдельные от RX — TX-спектр обычно нужен мельче по FFT
|
||||
// (TX-полоса узкая) и с другим усреднением (быстрее реагировать на голос).
|
||||
var
|
||||
AnalyzerFFT, AvgCount, WfAvgCount, MaxW, Overlap, DisplayPixels: Integer;
|
||||
Backmult, WfBackmult: Double;
|
||||
EffectiveFPS: Double;
|
||||
const KEEP_TIME_SEC = 0.10;
|
||||
begin
|
||||
AnalyzerFFT := NormalizeFFTSize(FTXFFTSize);
|
||||
FTXDispPos := 0;
|
||||
MaxW := AnalyzerFFT + Round(Min(KEEP_TIME_SEC * FSampleRate,
|
||||
KEEP_TIME_SEC * AnalyzerFFT * EnsureRange(FDisplayFPS, 5, 100)));
|
||||
CalcAnalyzerTiming(AnalyzerFFT, Overlap, EffectiveFPS);
|
||||
DisplayPixels := EnsureRange(FLastSpectrumW, 64, SPECTRUM_PIXELS);
|
||||
|
||||
AverageTimeToParams(FTXSpecAvgMode, FTXSpecAvgTimeMS, AvgCount, Backmult);
|
||||
AverageTimeToParams(FTXWfAvgMode, FTXWfAvgTimeMS, WfAvgCount, WfBackmult);
|
||||
|
||||
SetAnalyzer(TX_DISP_ID, 2, 1, 1, @FFlp[0], AnalyzerFFT, DISPLAY_BLOCK_SIZE,
|
||||
FTXWindowType, 14.0, Overlap, 0, 0.0, 0.0, DisplayPixels,
|
||||
1, 0, 0.0, 0.0, MaxW);
|
||||
SetDisplayDetectorMode(TX_DISP_ID, 0, FTXSpecDetector);
|
||||
SetDisplayAverageMode (TX_DISP_ID, 0, FTXSpecAvgMode);
|
||||
SetDisplayNumAverage (TX_DISP_ID, 0, AvgCount);
|
||||
SetDisplayAvBackmult (TX_DISP_ID, 0, Backmult);
|
||||
SetDisplayNormOneHz (TX_DISP_ID, 0, UsePanNormOneHz(FTXSpecDetector));
|
||||
SetDisplayDetectorMode(TX_DISP_ID, 1, FTXWfDetector);
|
||||
SetDisplayAverageMode (TX_DISP_ID, 1, FTXWfAvgMode);
|
||||
SetDisplayNumAverage (TX_DISP_ID, 1, WfAvgCount);
|
||||
SetDisplayAvBackmult (TX_DISP_ID, 1, WfBackmult);
|
||||
SetDisplayNormOneHz (TX_DISP_ID, 1, 0);
|
||||
SetDisplaySampleRate (TX_DISP_ID, FSampleRate);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ApplyAnalyzerSettings;
|
||||
// Совместимый вход: применяет настройки и к RX, и к TX analyzer'у.
|
||||
begin
|
||||
ApplyRXAnalyzerSettings;
|
||||
ApplyTXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.CloseAnalyzer;
|
||||
@@ -681,7 +887,8 @@ begin
|
||||
FDisplayThread.WaitFor;
|
||||
FreeAndNil(FDisplayThread);
|
||||
end;
|
||||
DestroyAnalyzer(DISP_ID);
|
||||
DestroyAnalyzer(RX_DISP_ID);
|
||||
DestroyAnalyzer(TX_DISP_ID);
|
||||
FAnalyzerOpen := False;
|
||||
end;
|
||||
|
||||
@@ -761,18 +968,13 @@ begin
|
||||
);
|
||||
|
||||
SetTXAMode(TXA_CHAN, ModeToWDSP(FMode));
|
||||
SetTXABandpassFreqs(TXA_CHAN, 100, 2800);
|
||||
SetTXABandpassWindow(TXA_CHAN, 1);
|
||||
SetTXALevelerSt(TXA_CHAN, 1);
|
||||
SetTXALevelerTop(TXA_CHAN, 5.0);
|
||||
SetTXAALCSt(TXA_CHAN, 1);
|
||||
SetTXAALCDecay(TXA_CHAN, 10);
|
||||
SetTXACompressorRun(TXA_CHAN, 0);
|
||||
SetTXAPanelGain1(TXA_CHAN, 1.0);
|
||||
SetTXAPanelRun(TXA_CHAN, 1);
|
||||
SetTXAPanelSelect(TXA_CHAN, 1);
|
||||
|
||||
FInitialized := True;
|
||||
// Прокидываем все TX-параметры из FTX*-полей: фильтр, mic gain, EQ,
|
||||
// Leveler, ALC, Compressor, Phase Rotator, AM Carrier, FM, CTCSS.
|
||||
ApplyTXChainSettings;
|
||||
ApplyNoiseFilterState;
|
||||
Result := True;
|
||||
|
||||
@@ -904,7 +1106,6 @@ end;
|
||||
procedure TWDSPEngine.FeedDisplaySample(const I, Q: Double);
|
||||
begin
|
||||
if not FAnalyzerOpen then Exit;
|
||||
if FTXActive then Exit;
|
||||
|
||||
FDispBuf[FDispPos * 2] := I;
|
||||
FDispBuf[FDispPos * 2 + 1] := Q;
|
||||
@@ -912,11 +1113,26 @@ begin
|
||||
|
||||
if FDispPos >= DISPLAY_BLOCK_SIZE then
|
||||
begin
|
||||
Spectrum0(1, DISP_ID, 0, 0, @FDispBuf[0]);
|
||||
Spectrum0(1, RX_DISP_ID, 0, 0, @FDispBuf[0]);
|
||||
FDispPos := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.FeedTXDisplaySample(const I, Q: Double);
|
||||
begin
|
||||
if not FAnalyzerOpen then Exit;
|
||||
|
||||
FTXDispBuf[FTXDispPos * 2] := I;
|
||||
FTXDispBuf[FTXDispPos * 2 + 1] := Q;
|
||||
Inc(FTXDispPos);
|
||||
|
||||
if FTXDispPos >= DISPLAY_BLOCK_SIZE then
|
||||
begin
|
||||
Spectrum0(1, TX_DISP_ID, 0, 0, @FTXDispBuf[0]);
|
||||
FTXDispPos := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ProcessRXBlock;
|
||||
var
|
||||
i: Integer;
|
||||
@@ -943,8 +1159,10 @@ begin
|
||||
// вызов вернёт 0/-∞ — отсюда "падение до нуля". Только здесь!
|
||||
FSMeter := GetRXAMeter(RXA_CHAN, RXA_S_AV);
|
||||
|
||||
// Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1)
|
||||
if Assigned(FOnAudio) and not FMuted then
|
||||
// Аудио колбэк — FAudioBufSize сэмплов @ FAudioRate (после децимации 4:1).
|
||||
// При TX RX-аудио глушим (как в Thetis/piHPSDR при не-duplex MOX),
|
||||
// иначе оператор слышит свой эфир через RX-цепь и получает feedback.
|
||||
if Assigned(FOnAudio) and not FMuted and not FTXActive then
|
||||
begin
|
||||
for i := 0 to FAudioBufSize - 1 do
|
||||
begin
|
||||
@@ -1227,6 +1445,7 @@ end;
|
||||
|
||||
procedure TWDSPEngine.SetMicGain(GainDB: Double);
|
||||
begin
|
||||
FTXMicGainDB := GainDB;
|
||||
if not FInitialized then Exit;
|
||||
SetTXAPanelGain1(TXA_CHAN, Power(10.0, GainDB / 20.0));
|
||||
end;
|
||||
@@ -1235,6 +1454,11 @@ procedure TWDSPEngine.SetTXRun(Run: Boolean);
|
||||
begin
|
||||
FTXActive := Run;
|
||||
FDispPos := 0;
|
||||
FTXDispPos := 0;
|
||||
if Run then
|
||||
FActiveDisplayID := TX_DISP_ID
|
||||
else
|
||||
FActiveDisplayID := RX_DISP_ID;
|
||||
|
||||
if Run then
|
||||
begin
|
||||
@@ -1299,10 +1523,14 @@ begin
|
||||
Err := 0;
|
||||
fexchange0(TXA_CHAN, @FTXIn[0], @FTXOut[0], @Err);
|
||||
|
||||
// TX сигнал → анализатор: спектр/водопад показывают переданный сигнал
|
||||
// FTXOut: FBufSize IQ пар @ FSampleRate — тот же формат что и FSpecBuf
|
||||
// TX сигнал → отдельный TX-analyzer (TX_DISP_ID).
|
||||
// Подаём чанками по DISPLAY_BLOCK_SIZE — analyzer ожидает именно такой
|
||||
// размер блока (см. SetAnalyzer). Если подавать FTXOut целиком
|
||||
// (FBufSize пар), analyzer прочитает только первые DISPLAY_BLOCK_SIZE
|
||||
// и проигнорирует остальное → спектр будет пустой.
|
||||
if FAnalyzerOpen then
|
||||
Spectrum0(1, DISP_ID, 0, 0, @FTXOut[0]);
|
||||
for i := 0 to FBufSize - 1 do
|
||||
FeedTXDisplaySample(FTXOut[i * 2], FTXOut[i * 2 + 1]);
|
||||
|
||||
// FTXOut содержит FBufSize IQ пар @ FSampleRate (interleaved double)
|
||||
if Assigned(FOnTXIQ) then
|
||||
@@ -1335,6 +1563,236 @@ begin
|
||||
RTLEventSetEvent(FTXMicSem); // будим TX поток
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.PushTXMicSampleD(const S: Double);
|
||||
var newHead: Integer;
|
||||
begin
|
||||
newHead := (FTXMicHead + 1) and (TX_MIC_RING - 1);
|
||||
if newHead <> FTXMicTail then
|
||||
begin
|
||||
FTXMicRing[FTXMicHead] := S;
|
||||
FTXMicHead := newHead;
|
||||
end;
|
||||
// overrun → дроп, как в HW path
|
||||
RTLEventSetEvent(FTXMicSem);
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TX chain: применение всех TX-параметров к WDSP-каналу
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
procedure TWDSPEngine.ApplyTXChainSettings;
|
||||
// Вызывается после OpenChannel(TXA) и при старте TX. Прокидывает все
|
||||
// FTX*-поля в WDSP. Идемпотентна, ничего не меняет если канал не открыт.
|
||||
var
|
||||
i: Integer;
|
||||
Gains, Freqs, Qs: array[0..10] of Double;
|
||||
begin
|
||||
if not FInitialized then Exit;
|
||||
|
||||
// Bandpass
|
||||
if (FTXFilterLow > 0) and (FTXFilterHigh > FTXFilterLow) then
|
||||
SetTXABandpassFreqs(TXA_CHAN, FTXFilterLow, FTXFilterHigh);
|
||||
if FTXFilterWindow >= 0 then
|
||||
SetTXABandpassWindow(TXA_CHAN, FTXFilterWindow);
|
||||
if FTXFilterNC > 0 then
|
||||
TXASetNC(TXA_CHAN, FTXFilterNC);
|
||||
if FTXFilterMP >= 0 then
|
||||
TXASetMP(TXA_CHAN, FTXFilterMP);
|
||||
|
||||
// Mic gain (panel gain1)
|
||||
SetTXAPanelGain1(TXA_CHAN, Power(10.0, FTXMicGainDB / 20.0));
|
||||
|
||||
// Compressor
|
||||
SetTXACompressorRun(TXA_CHAN, Ord(FTXCompressorOn));
|
||||
SetTXACompressorGain(TXA_CHAN, FTXCompressorGain);
|
||||
|
||||
// Leveler
|
||||
SetTXALevelerSt(TXA_CHAN, Ord(FTXLevelerOn));
|
||||
SetTXALevelerTop(TXA_CHAN, FTXLevelerTop);
|
||||
SetTXALevelerDecay(TXA_CHAN, FTXLevelerDecay);
|
||||
|
||||
// ALC
|
||||
SetTXAALCSt(TXA_CHAN, Ord(FTXALCOn));
|
||||
SetTXAALCMaxGain(TXA_CHAN, FTXALCMaxGain);
|
||||
SetTXAALCDecay(TXA_CHAN, FTXALCDecay);
|
||||
|
||||
// Phase Rotator
|
||||
SetTXAPHROTRun(TXA_CHAN, Ord(FTXPhaseRotOn));
|
||||
SetTXAPHROTNstages(TXA_CHAN, FTXPhaseRotStages);
|
||||
SetTXAPHROTCorner(TXA_CHAN, FTXPhaseRotFreq);
|
||||
|
||||
// EQ TX (10-band: [0]=preamp, [1..10]=bands)
|
||||
for i := 0 to 10 do
|
||||
begin
|
||||
Gains[i] := FTXEQGains[i];
|
||||
Freqs[i] := FTXEQFreqs[i];
|
||||
Qs[i] := 0.707; // умолчание Q
|
||||
end;
|
||||
SetTXAEQProfile(TXA_CHAN, FTXEQNumBands, @Freqs[0], @Gains[0]);
|
||||
SetTXAEQRun(TXA_CHAN, Ord(FTXEQOn));
|
||||
|
||||
// AM
|
||||
SetTXAAMCarrierLevel(TXA_CHAN, FTXAMCarrier);
|
||||
|
||||
// FM
|
||||
SetTXAFMDeviation(TXA_CHAN, FTXFMDeviation);
|
||||
if (FTXFMLowCut < FTXFMHighCut) then
|
||||
SetTXAFMAFFilter(TXA_CHAN, FTXFMLowCut, FTXFMHighCut);
|
||||
SetTXAFMEmphPosition(TXA_CHAN, FTXFMEmphPos);
|
||||
|
||||
// CTCSS
|
||||
SetTXACTCSSFreq(TXA_CHAN, FTXCTCSSFreq);
|
||||
SetTXACTCSSRun(TXA_CHAN, Ord(FTXCTCSSOn));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXFilterFull(Low, High, NC, MP, Window: Integer);
|
||||
begin
|
||||
FTXFilterLow := Low;
|
||||
FTXFilterHigh := High;
|
||||
FTXFilterNC := NC;
|
||||
FTXFilterMP := MP;
|
||||
FTXFilterWindow := Window;
|
||||
FFilterLow := Low;
|
||||
FFilterHigh := High;
|
||||
if not FInitialized then Exit;
|
||||
SetTXABandpassFreqs(TXA_CHAN, Low, High);
|
||||
SetTXABandpassWindow(TXA_CHAN, Window);
|
||||
if NC > 0 then TXASetNC(TXA_CHAN, NC);
|
||||
if MP >= 0 then TXASetMP(TXA_CHAN, MP);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXCompressor(Enable: Boolean; GainDB: Double);
|
||||
begin
|
||||
FTXCompressorOn := Enable;
|
||||
FTXCompressorGain := GainDB;
|
||||
if not FInitialized then Exit;
|
||||
SetTXACompressorGain(TXA_CHAN, GainDB);
|
||||
SetTXACompressorRun(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXLeveler(Enable: Boolean; TopDB: Double; DecayMS: Integer);
|
||||
begin
|
||||
FTXLevelerOn := Enable;
|
||||
FTXLevelerTop := TopDB;
|
||||
FTXLevelerDecay := DecayMS;
|
||||
if not FInitialized then Exit;
|
||||
SetTXALevelerTop(TXA_CHAN, TopDB);
|
||||
SetTXALevelerDecay(TXA_CHAN, DecayMS);
|
||||
SetTXALevelerSt(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXALC(Enable: Boolean; MaxGainDB: Double; DecayMS: Integer);
|
||||
begin
|
||||
FTXALCOn := Enable;
|
||||
FTXALCMaxGain := MaxGainDB;
|
||||
FTXALCDecay := DecayMS;
|
||||
if not FInitialized then Exit;
|
||||
SetTXAALCMaxGain(TXA_CHAN, MaxGainDB);
|
||||
SetTXAALCDecay(TXA_CHAN, DecayMS);
|
||||
SetTXAALCSt(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXPhaseRot(Enable: Boolean; Stages: Integer; FreqHz: Double);
|
||||
begin
|
||||
FTXPhaseRotOn := Enable;
|
||||
FTXPhaseRotStages := Stages;
|
||||
FTXPhaseRotFreq := FreqHz;
|
||||
if not FInitialized then Exit;
|
||||
SetTXAPHROTNstages(TXA_CHAN, Stages);
|
||||
SetTXAPHROTCorner(TXA_CHAN, FreqHz);
|
||||
SetTXAPHROTRun(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXEQ(Enable: Boolean; NumBands: Integer;
|
||||
const Gains, Freqs: array of Double);
|
||||
var
|
||||
i, n: Integer;
|
||||
G, F: array[0..10] of Double;
|
||||
begin
|
||||
FTXEQOn := Enable;
|
||||
FTXEQNumBands := NumBands;
|
||||
n := Min(11, Min(Length(Gains), Length(Freqs)));
|
||||
for i := 0 to n - 1 do
|
||||
begin
|
||||
FTXEQGains[i] := Gains[i];
|
||||
FTXEQFreqs[i] := Freqs[i];
|
||||
G[i] := Gains[i];
|
||||
F[i] := Freqs[i];
|
||||
end;
|
||||
if not FInitialized then Exit;
|
||||
SetTXAEQProfile(TXA_CHAN, NumBands, @F[0], @G[0]);
|
||||
SetTXAEQRun(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXAMCarrierLevel(Level: Double);
|
||||
begin
|
||||
FTXAMCarrier := Level;
|
||||
if not FInitialized then Exit;
|
||||
WDSP.SetTXAAMCarrierLevel(TXA_CHAN, Level);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXFMParams(Deviation: Double; LowCut, HighCut, EmphPos: Integer);
|
||||
begin
|
||||
FTXFMDeviation := Deviation;
|
||||
FTXFMLowCut := LowCut;
|
||||
FTXFMHighCut := HighCut;
|
||||
FTXFMEmphPos := EmphPos;
|
||||
if not FInitialized then Exit;
|
||||
SetTXAFMDeviation(TXA_CHAN, Deviation);
|
||||
if LowCut < HighCut then
|
||||
SetTXAFMAFFilter(TXA_CHAN, LowCut, HighCut);
|
||||
SetTXAFMEmphPosition(TXA_CHAN, EmphPos);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXCTCSS(Enable: Boolean; FreqHz: Double);
|
||||
begin
|
||||
FTXCTCSSOn := Enable;
|
||||
FTXCTCSSFreq := FreqHz;
|
||||
if not FInitialized then Exit;
|
||||
SetTXACTCSSFreq(TXA_CHAN, FreqHz);
|
||||
SetTXACTCSSRun(TXA_CHAN, Ord(Enable));
|
||||
end;
|
||||
|
||||
function TWDSPEngine.TXMicRingAvail: Integer;
|
||||
begin
|
||||
Result := (FTXMicHead - FTXMicTail + TX_MIC_RING) and (TX_MIC_RING - 1);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXMicSource(Source: TTXMicSource);
|
||||
begin
|
||||
if FTXMicSource = Source then Exit;
|
||||
FTXMicSource := Source;
|
||||
// При смене источника сбрасываем ring, чтобы не подмешивать остатки
|
||||
// от предыдущего producer'а (race window игнорируется — TX обычно неактивен).
|
||||
FTXMicHead := 0;
|
||||
FTXMicTail := 0;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXFFTParams(FFTSize, WinType: Integer);
|
||||
begin
|
||||
FTXFFTSize := NormalizeFFTSize(FFTSize);
|
||||
FTXWindowType := EnsureRange(WinType, 0, 6);
|
||||
if FAnalyzerOpen then ApplyTXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXSpectrumDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
|
||||
begin
|
||||
NormalizeDisplayParams(Detector, AvgMode, AvgTimeMS, False);
|
||||
FTXSpecDetector := Detector;
|
||||
FTXSpecAvgMode := AvgMode;
|
||||
FTXSpecAvgTimeMS := AvgTimeMS;
|
||||
if FAnalyzerOpen then ApplyTXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetTXWaterfallDisplay(Detector, AvgMode: Integer; AvgTimeMS: Double);
|
||||
begin
|
||||
NormalizeDisplayParams(Detector, AvgMode, AvgTimeMS, True);
|
||||
FTXWfDetector := Detector;
|
||||
FTXWfAvgMode := AvgMode;
|
||||
FTXWfAvgTimeMS := AvgTimeMS;
|
||||
if FAnalyzerOpen then ApplyTXAnalyzerSettings;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Spectrum
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1350,12 +1808,14 @@ var
|
||||
PixBuf: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||
Flag: Integer;
|
||||
i, N: Integer;
|
||||
DispID: Integer;
|
||||
begin
|
||||
if not FInitialized or not FAnalyzerOpen then Exit;
|
||||
N := EnsureRange(FDisplayPixelCount, 1, SPECTRUM_PIXELS);
|
||||
DispID := FActiveDisplayID; // RX_DISP_ID или TX_DISP_ID, зависит от FTXActive
|
||||
|
||||
Flag := 0;
|
||||
GetPixels(DISP_ID, 0, @PixBuf[0], @Flag);
|
||||
GetPixels(DispID, 0, @PixBuf[0], @Flag);
|
||||
if Flag <> 0 then
|
||||
begin
|
||||
for i := 0 to N - 1 do
|
||||
@@ -1365,7 +1825,7 @@ begin
|
||||
end;
|
||||
|
||||
Flag := 0;
|
||||
GetPixels(DISP_ID, 1, @PixBuf[0], @Flag);
|
||||
GetPixels(DispID, 1, @PixBuf[0], @Flag);
|
||||
if Flag <> 0 then
|
||||
begin
|
||||
for i := 0 to N - 1 do
|
||||
@@ -1512,11 +1972,11 @@ begin
|
||||
FSpecAvgTimeMS := AvgTimeMS;
|
||||
if not FAnalyzerOpen then Exit;
|
||||
AverageTimeToParams(FSpecAvgMode, FSpecAvgTimeMS, AvgCount, Backmult);
|
||||
SetDisplayDetectorMode(DISP_ID, 0, FSpecDetector);
|
||||
SetDisplayAverageMode(DISP_ID, 0, FSpecAvgMode);
|
||||
SetDisplayNumAverage(DISP_ID, 0, AvgCount);
|
||||
SetDisplayAvBackmult(DISP_ID, 0, Backmult);
|
||||
SetDisplayNormOneHz(DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
|
||||
SetDisplayDetectorMode(RX_DISP_ID, 0, FSpecDetector);
|
||||
SetDisplayAverageMode(RX_DISP_ID, 0, FSpecAvgMode);
|
||||
SetDisplayNumAverage(RX_DISP_ID, 0, AvgCount);
|
||||
SetDisplayAvBackmult(RX_DISP_ID, 0, Backmult);
|
||||
SetDisplayNormOneHz(RX_DISP_ID, 0, UsePanNormOneHz(FSpecDetector));
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetWaterfallDisplay(Detector, AvgMode: Integer;
|
||||
@@ -1536,11 +1996,11 @@ begin
|
||||
FWfAvgTimeMS := AvgTimeMS;
|
||||
if not FAnalyzerOpen then Exit;
|
||||
AverageTimeToParams(FWfAvgMode, FWfAvgTimeMS, AvgCount, Backmult);
|
||||
SetDisplayDetectorMode(DISP_ID, 1, FWfDetector);
|
||||
SetDisplayAverageMode(DISP_ID, 1, FWfAvgMode);
|
||||
SetDisplayNumAverage(DISP_ID, 1, AvgCount);
|
||||
SetDisplayAvBackmult(DISP_ID, 1, Backmult);
|
||||
SetDisplayNormOneHz(DISP_ID, 1, 0);
|
||||
SetDisplayDetectorMode(RX_DISP_ID, 1, FWfDetector);
|
||||
SetDisplayAverageMode(RX_DISP_ID, 1, FWfAvgMode);
|
||||
SetDisplayNumAverage(RX_DISP_ID, 1, AvgCount);
|
||||
SetDisplayAvBackmult(RX_DISP_ID, 1, Backmult);
|
||||
SetDisplayNormOneHz(RX_DISP_ID, 1, 0);
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user