mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
fix(display): хирургическая смена rate главного — паны не замирают
ChangeSampleRate больше не делает полный Close/Open движка (стоп DSP-потока, разрушение всех анализаторов, сброс общей IQ-очереди): гейт FMainGated+FMainLock дропает только main-пакеты, пересоздаются только RXA_CHAN и слайсы пана 0, анализаторы панов живут. Контроллер не сбрасывает FWDSPReady на время смены (OnDDCIQ продолжает кормить паны). Общий код RXA — Open/CloseMainRXChannel. Бонус: SetChannelState dmode=1 на idle-каналах зря ждал флаш ~130мс → dmode=0 в CloseSliceChannel/CloseMainRXChannel (каналы всегда idle по локам). Итог: смена 331→120мс, пауза аудио пан-слайса 137→40мс. Смоук ratetest ALL PASS, подтверждено на экране. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+174
-71
@@ -284,6 +284,13 @@ type
|
||||
FQueueTail: Integer; // читает DSP поток
|
||||
FQueueSem: PRTLEvent; // сигнал: есть новые данные (RTLEvent)
|
||||
FDSPRunning: Boolean;
|
||||
// Гейт главного тракта на время хирургической смены rate (ChangeSampleRate):
|
||||
// True = DSP-поток дропает main-пакеты (PanId=0) не блокируясь, паны
|
||||
// обрабатываются как обычно. FMainLock — барьер: DSP-поток держит его на
|
||||
// время обработки main-пакета, ChangeSampleRate делает Enter/Leave после
|
||||
// установки гейта, гарантируя что fexchange0 на RXA_CHAN завершён.
|
||||
FMainGated: Boolean;
|
||||
FMainLock: TCriticalSection;
|
||||
|
||||
FSpectrumPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||
FWaterfallPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||
@@ -461,6 +468,8 @@ type
|
||||
procedure PushPanItemToDSP(PanId: Integer; const Item: TIQQueueItem);
|
||||
procedure ProcessRXBlock;
|
||||
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
||||
procedure OpenMainRXChannel; // RXA_CHAN под текущие FBufSize/FSampleRate
|
||||
procedure CloseMainRXChannel; // state=0 + EXT-блоки + CloseChannel(RXA_CHAN)
|
||||
procedure OpenAnalyzer;
|
||||
procedure CloseAnalyzer;
|
||||
procedure ApplyAnalyzerSettings;
|
||||
@@ -877,7 +886,22 @@ begin
|
||||
begin
|
||||
Item := @FEngine.FQueue[FEngine.FQueueTail];
|
||||
if Item^.PanId = 0 then
|
||||
FEngine.PushIQItemToDSP(Item^)
|
||||
begin
|
||||
// Гейт смены rate главного (ChangeSampleRate): main-пакеты дропаем
|
||||
// НЕ блокируясь — пан-пакеты позади в очереди не должны ждать
|
||||
// пересборку RXA. Повторная проверка под локом закрывает гонку
|
||||
// с установкой гейта из UI-потока.
|
||||
if not FEngine.FMainGated then
|
||||
begin
|
||||
FEngine.FMainLock.Enter;
|
||||
try
|
||||
if not FEngine.FMainGated then
|
||||
FEngine.PushIQItemToDSP(Item^);
|
||||
finally
|
||||
FEngine.FMainLock.Leave;
|
||||
end;
|
||||
end;
|
||||
end
|
||||
else
|
||||
FEngine.PushPanItemToDSP(Item^.PanId, Item^);
|
||||
FEngine.FQueueTail := (FEngine.FQueueTail + 1) and (IQ_QUEUE_SIZE - 1);
|
||||
@@ -1130,6 +1154,10 @@ begin
|
||||
FDSPThread := nil;
|
||||
FDisplayThread := nil;
|
||||
|
||||
// Гейт главного тракта (хирургическая смена rate)
|
||||
FMainGated := False;
|
||||
FMainLock := TCriticalSection.Create;
|
||||
|
||||
// Мультислайсы
|
||||
FSliceLock := TCriticalSection.Create;
|
||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||
@@ -1212,6 +1240,7 @@ begin
|
||||
RTLEventDestroy(FQueueSem);
|
||||
RTLEventDestroy(FTXMicSem);
|
||||
FSliceLock.Free;
|
||||
FMainLock.Free;
|
||||
inherited;
|
||||
end;
|
||||
|
||||
@@ -1515,38 +1544,11 @@ begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
OpenChannel(
|
||||
RXA_CHAN,
|
||||
FBufSize,
|
||||
FAudioBufSize,
|
||||
FSampleRate,
|
||||
FAudioRate,
|
||||
FAudioRate,
|
||||
0,
|
||||
0, // state=0: создать но не запускать
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
0
|
||||
);
|
||||
|
||||
// Каналы только что созданы с dsp_rate = FAudioRate; забываем «широкую»
|
||||
// геометрию прошлой сессии (Close/Open через ChangeSampleRate).
|
||||
FRXDSPRate := FAudioRate;
|
||||
OpenMainRXChannel;
|
||||
// TXA только что создан узким; забываем «широкую» геометрию прошлой
|
||||
// сессии (RX-половину сбрасывает OpenMainRXChannel).
|
||||
FTXDSPSize := FAudioBufSize;
|
||||
|
||||
create_anbEXT(RXA_CHAN, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
|
||||
SetRXABandpassWindow(RXA_CHAN, 1);
|
||||
SetRXAMode(RXA_CHAN, ModeToWDSP(FMode));
|
||||
SetRXAShiftRun(RXA_CHAN, 1);
|
||||
SetRXAShiftFreq(RXA_CHAN, 0.0);
|
||||
RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh);
|
||||
SetAGC(FAGCMode, 50.0);
|
||||
SetRXAPanelGain1(RXA_CHAN, FVolume);
|
||||
SetRXAPanelSelect(RXA_CHAN, 3);
|
||||
SetRXAPanelRun(RXA_CHAN, 1);
|
||||
SetChannelState(RXA_CHAN, 1, 0);
|
||||
|
||||
// TXA: mic @ FAudioRate → IQ @ FTXSampleRate (фиксированный DUC rate, не зависит от RX).
|
||||
// out_size = FAudioBufSize * FTXSampleRate/FAudioRate = FTXOutBufSize (4096 @ 192k tx).
|
||||
// FTXOut уже имеет размер FTXOutBufSize*2.
|
||||
@@ -1649,56 +1651,153 @@ begin
|
||||
end;
|
||||
|
||||
CloseAnalyzer;
|
||||
destroy_anbEXT(RXA_CHAN);
|
||||
destroy_nobEXT(RXA_CHAN);
|
||||
CloseChannel(RXA_CHAN);
|
||||
CloseMainRXChannel;
|
||||
CloseChannel(TXA_CHAN);
|
||||
FInitialized := False;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.OpenMainRXChannel;
|
||||
// Открывает RXA_CHAN под текущие FBufSize/FSampleRate и восстанавливает его
|
||||
// состояние из полей движка. Канал создаётся «узким» (dsp_rate = FAudioRate);
|
||||
// широкий WFM поднимает вызывающий через SetMode (ApplyDSPRate).
|
||||
begin
|
||||
OpenChannel(
|
||||
RXA_CHAN,
|
||||
FBufSize,
|
||||
FAudioBufSize,
|
||||
FSampleRate,
|
||||
FAudioRate,
|
||||
FAudioRate,
|
||||
0,
|
||||
0, // state=0: создать но не запускать
|
||||
0.010, 0.010, 0.010, 0.010,
|
||||
0
|
||||
);
|
||||
FRXDSPRate := FAudioRate;
|
||||
|
||||
create_anbEXT(RXA_CHAN, 1, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
create_nobEXT(RXA_CHAN, 1, 0, FBufSize, FSampleRate, 0.0001, 0.0001, 0.0001, 0.05, 20);
|
||||
|
||||
SetRXABandpassWindow(RXA_CHAN, 1);
|
||||
SetRXAMode(RXA_CHAN, ModeToWDSP(FMode));
|
||||
SetRXAShiftRun(RXA_CHAN, 1);
|
||||
SetRXAShiftFreq(RXA_CHAN, FShiftHz);
|
||||
RXASetPassband(RXA_CHAN, FFilterLow, FFilterHigh);
|
||||
SetAGC(FAGCMode, 50.0);
|
||||
SetRXAPanelGain1(RXA_CHAN, FVolume);
|
||||
SetRXAPanelSelect(RXA_CHAN, 3);
|
||||
SetRXAPanelRun(RXA_CHAN, 1);
|
||||
SetChannelState(RXA_CHAN, 1, 0);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.CloseMainRXChannel;
|
||||
// dmode=0: канал к этому моменту гарантированно idle (Close — DSP-поток уже
|
||||
// мёртв; ChangeSampleRate — FMainGated+барьер), а dmode=1 на простаивающем
|
||||
// канале зря ждёт флаш ~130мс.
|
||||
begin
|
||||
SetChannelState(RXA_CHAN, 0, 0);
|
||||
destroy_anbEXT(RXA_CHAN);
|
||||
destroy_nobEXT(RXA_CHAN);
|
||||
CloseChannel(RXA_CHAN);
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.ChangeSampleRate(NewRate: Integer);
|
||||
// Меняет samplerate на лету: Close → сброс очереди → пересчёт буферов → Open.
|
||||
// Панов не трогает: их rate свой; аккумуляторы сбрасываем (очередь общая,
|
||||
// их пакеты умерли вместе с ней), анализаторы пересоздаст OpenAnalyzer.
|
||||
// Смена rate главного тракта ХИРУРГИЧЕСКИ: DSP-поток, display-поток,
|
||||
// анализаторы и паны N живут — закрываем/переоткрываем только RXA_CHAN и
|
||||
// слайс-каналы пана 0. Пакеты панов идут через общую очередь без пауз:
|
||||
// DSP-поток лишь дропает main-пакеты, пока стоит FMainGated. (Раньше здесь
|
||||
// был полный Close/Open движка — паны замирали на всё время пересборки.)
|
||||
var
|
||||
p: Integer;
|
||||
i: Integer;
|
||||
begin
|
||||
if NewRate = FSampleRate then Exit;
|
||||
|
||||
// Останавливаем текущий канал
|
||||
Close;
|
||||
if not FInitialized then
|
||||
begin
|
||||
// Движок не открыт: только запоминаем параметры — Open создаст всё сам.
|
||||
FSampleRate := NewRate;
|
||||
FBufSize := FAudioBufSize * NewRate div FAudioRate;
|
||||
SetLength(FRXIn, FBufSize * 2);
|
||||
SetLength(FRXOut, FBufSize * 2);
|
||||
SetLength(FSpecBuf, FBufSize * 2);
|
||||
SetLength(FRXAccI, FBufSize);
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
FRXAccPos := 0;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Сбрасываем очередь IQ пакетов — старые пакеты не совместимы с новым rate.
|
||||
// Без этого DSP поток тратит секунды на разбор накопившихся пакетов.
|
||||
FQueueHead := 0;
|
||||
FQueueTail := 0;
|
||||
FRXAccPos := 0;
|
||||
for p := 1 to MAX_PANS - 1 do
|
||||
if FPans[p].Active then
|
||||
begin
|
||||
FPans[p].AccPos := 0;
|
||||
FPans[p].DispPos := 0;
|
||||
// Гейт: DSP-поток перестаёт брать main-пакеты (дропает их из очереди);
|
||||
// барьер Enter/Leave гарантирует, что текущий PushIQItemToDSP
|
||||
// (fexchange0 на RXA_CHAN) завершён.
|
||||
FMainGated := True;
|
||||
FMainLock.Enter;
|
||||
FMainLock.Leave;
|
||||
try
|
||||
try
|
||||
// Слайсы пана 0 открыты с InRate = главный rate — закрываем на пересборку.
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Opened and (FSlices[i].PanId = 0) then
|
||||
CloseSliceChannel(i);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
CloseMainRXChannel;
|
||||
|
||||
// Новый rate/буферы главного тракта.
|
||||
FSampleRate := NewRate;
|
||||
FBufSize := FAudioBufSize * NewRate div FAudioRate;
|
||||
SetLength(FRXIn, FBufSize * 2);
|
||||
SetLength(FRXOut, FBufSize * 2);
|
||||
SetLength(FSpecBuf, FBufSize * 2);
|
||||
SetLength(FRXAccI, FBufSize);
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
FRXAccPos := 0;
|
||||
FDispPos := 0;
|
||||
AllocSliceBuffers;
|
||||
|
||||
// Переоткрываем главный канал и слайсы пана 0 (состояние из полей/записей).
|
||||
OpenMainRXChannel;
|
||||
if FMode = MODE_WFM then
|
||||
begin
|
||||
// Канал создан узким — SetMode поднимет dsp_rate под новый FSampleRate,
|
||||
// SetFilter пересчитает полосу (как в Open).
|
||||
SetMode(FMode);
|
||||
SetFilter(FFilterLow, FFilterHigh);
|
||||
end;
|
||||
ApplyNoiseFilterState;
|
||||
|
||||
FSliceLock.Enter;
|
||||
try
|
||||
for i := 0 to MAX_SLICES - 1 do
|
||||
if FSlices[i].Active and (not FSlices[i].Opened)
|
||||
and (FSlices[i].PanId = 0) then
|
||||
OpenSliceChannel(i);
|
||||
finally
|
||||
FSliceLock.Leave;
|
||||
end;
|
||||
|
||||
// RX-анализатор не разрушался — переармируем под новый rate. FFT-история
|
||||
// главного дисплея сбрасывается (при смене rate это неизбежно), паны
|
||||
// не затронуты.
|
||||
if FAnalyzerOpen then ApplyRXAnalyzerSettings;
|
||||
|
||||
// Beacon-декодер пересчитывает децимацию/RRC под новый IQ-rate.
|
||||
if FBeaconDec <> nil then FBeaconDec.Configure(FSampleRate);
|
||||
except
|
||||
on E: Exception do
|
||||
begin
|
||||
// Главный тракт в неопределённом состоянии — гасим FInitialized:
|
||||
// ProcessRXBlock/слайсы/паны выходят по нему, контроллер увидит
|
||||
// по Initialized и сбросит FWDSPReady.
|
||||
FLastError := E.ClassName + ': ' + E.Message;
|
||||
FInitialized := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Обновляем параметры
|
||||
FSampleRate := NewRate;
|
||||
FBufSize := FAudioBufSize * NewRate div FAudioRate;
|
||||
|
||||
// Перераспределяем буферы под новый размер
|
||||
SetLength(FRXIn, FBufSize * 2);
|
||||
SetLength(FRXOut, FBufSize * 2);
|
||||
SetLength(FSpecBuf, FBufSize * 2);
|
||||
SetLength(FRXAccI, FBufSize);
|
||||
SetLength(FRXAccQ, FBufSize);
|
||||
SetLength(FDispBuf, DISPLAY_BLOCK_SIZE * 2);
|
||||
FDispPos := 0;
|
||||
AllocSliceBuffers;
|
||||
|
||||
// Переинициализируем WDSP с новым rate
|
||||
Open;
|
||||
|
||||
// Beacon-декодер пересчитывает децимацию/RRC под новый IQ-rate.
|
||||
if FBeaconDec <> nil then FBeaconDec.Configure(FSampleRate);
|
||||
finally
|
||||
FMainGated := False;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.SetBeaconDecoder(D: TBeaconDecoder);
|
||||
@@ -2112,9 +2211,13 @@ begin
|
||||
end;
|
||||
|
||||
procedure TWDSPEngine.CloseSliceChannel(Idx: Integer);
|
||||
// dmode=0: fexchange0 слайс-каналов зовётся ТОЛЬКО под FSliceLock (ProcessSlices/
|
||||
// PushPanItemToDSP), и все вызовы CloseSliceChannel — под тем же локом, т.е.
|
||||
// канал заведомо idle. dmode=1 здесь зря ждал флаш ~130мс на канал — при смене
|
||||
// rate главного эта пауза (под локом) замораживала обработку панов.
|
||||
begin
|
||||
if not FSlices[Idx].Opened then Exit;
|
||||
SetChannelState(FSlices[Idx].Chan, 0, 1);
|
||||
SetChannelState(FSlices[Idx].Chan, 0, 0);
|
||||
destroy_anbEXT(FSlices[Idx].Chan);
|
||||
destroy_nobEXT(FSlices[Idx].Chan);
|
||||
CloseChannel(FSlices[Idx].Chan);
|
||||
|
||||
Reference in New Issue
Block a user