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:
+8
-5
@@ -3167,14 +3167,17 @@ begin
|
|||||||
FSpanHz := Hz;
|
FSpanHz := Hz;
|
||||||
FBandCache[FCurrentBand].SpanHz := FSpanHz; // для обратной совместимости
|
FBandCache[FCurrentBand].SpanHz := FSpanHz; // для обратной совместимости
|
||||||
|
|
||||||
// 1. Останавливаем WDSP и меняем rate (Close + FlushQueue + Open).
|
// 1. Меняем rate главного тракта. Хирургически: движок гейтит и дропает
|
||||||
|
// только main-пакеты, DSP-поток/анализаторы/паны N живут без пауз —
|
||||||
|
// поэтому FWDSPReady НЕ сбрасываем (иначе OnDDCIQ перестал бы кормить
|
||||||
|
// паны на всё время пересборки и их водопады замирали).
|
||||||
if FWDSPReady and Assigned(FDSPEngine) then
|
if FWDSPReady and Assigned(FDSPEngine) then
|
||||||
begin
|
|
||||||
FWDSPReady := False;
|
|
||||||
FDSPEngine.ChangeSampleRate(Hz);
|
FDSPEngine.ChangeSampleRate(Hz);
|
||||||
end;
|
|
||||||
|
|
||||||
// 2. Новый rate трансиверу (после остановки DSP — пакеты сразу с верным rate).
|
// 2. Новый rate трансиверу. Короткий хвост пакетов старого rate, дошедших
|
||||||
|
// до переключения радио, попадает в новый канал — транзиент в единицы мс
|
||||||
|
// (то же окно было и при старой схеме: FWDSPReady поднимался до того, как
|
||||||
|
// радио реально применяло DDC Specific).
|
||||||
if Assigned(FNetwork) and FNetwork.Connected then
|
if Assigned(FNetwork) and FNetwork.Connected then
|
||||||
FNetwork.ConfigureDDCs(1, Hz div 1000, FMainADCSrc,
|
FNetwork.ConfigureDDCs(1, Hz div 1000, FMainADCSrc,
|
||||||
FDitherEnabled, FRandomEnabled);
|
FDitherEnabled, FRandomEnabled);
|
||||||
|
|||||||
+174
-71
@@ -284,6 +284,13 @@ type
|
|||||||
FQueueTail: Integer; // читает DSP поток
|
FQueueTail: Integer; // читает DSP поток
|
||||||
FQueueSem: PRTLEvent; // сигнал: есть новые данные (RTLEvent)
|
FQueueSem: PRTLEvent; // сигнал: есть новые данные (RTLEvent)
|
||||||
FDSPRunning: Boolean;
|
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;
|
FSpectrumPixels: array[0..SPECTRUM_PIXELS - 1] of Single;
|
||||||
FWaterfallPixels: 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 PushPanItemToDSP(PanId: Integer; const Item: TIQQueueItem);
|
||||||
procedure ProcessRXBlock;
|
procedure ProcessRXBlock;
|
||||||
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
procedure PushIQItemToDSP(const Item: TIQQueueItem);
|
||||||
|
procedure OpenMainRXChannel; // RXA_CHAN под текущие FBufSize/FSampleRate
|
||||||
|
procedure CloseMainRXChannel; // state=0 + EXT-блоки + CloseChannel(RXA_CHAN)
|
||||||
procedure OpenAnalyzer;
|
procedure OpenAnalyzer;
|
||||||
procedure CloseAnalyzer;
|
procedure CloseAnalyzer;
|
||||||
procedure ApplyAnalyzerSettings;
|
procedure ApplyAnalyzerSettings;
|
||||||
@@ -877,7 +886,22 @@ begin
|
|||||||
begin
|
begin
|
||||||
Item := @FEngine.FQueue[FEngine.FQueueTail];
|
Item := @FEngine.FQueue[FEngine.FQueueTail];
|
||||||
if Item^.PanId = 0 then
|
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
|
else
|
||||||
FEngine.PushPanItemToDSP(Item^.PanId, Item^);
|
FEngine.PushPanItemToDSP(Item^.PanId, Item^);
|
||||||
FEngine.FQueueTail := (FEngine.FQueueTail + 1) and (IQ_QUEUE_SIZE - 1);
|
FEngine.FQueueTail := (FEngine.FQueueTail + 1) and (IQ_QUEUE_SIZE - 1);
|
||||||
@@ -1130,6 +1154,10 @@ begin
|
|||||||
FDSPThread := nil;
|
FDSPThread := nil;
|
||||||
FDisplayThread := nil;
|
FDisplayThread := nil;
|
||||||
|
|
||||||
|
// Гейт главного тракта (хирургическая смена rate)
|
||||||
|
FMainGated := False;
|
||||||
|
FMainLock := TCriticalSection.Create;
|
||||||
|
|
||||||
// Мультислайсы
|
// Мультислайсы
|
||||||
FSliceLock := TCriticalSection.Create;
|
FSliceLock := TCriticalSection.Create;
|
||||||
FillChar(FSlices, SizeOf(FSlices), 0);
|
FillChar(FSlices, SizeOf(FSlices), 0);
|
||||||
@@ -1212,6 +1240,7 @@ begin
|
|||||||
RTLEventDestroy(FQueueSem);
|
RTLEventDestroy(FQueueSem);
|
||||||
RTLEventDestroy(FTXMicSem);
|
RTLEventDestroy(FTXMicSem);
|
||||||
FSliceLock.Free;
|
FSliceLock.Free;
|
||||||
|
FMainLock.Free;
|
||||||
inherited;
|
inherited;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1515,38 +1544,11 @@ begin
|
|||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
OpenChannel(
|
OpenMainRXChannel;
|
||||||
RXA_CHAN,
|
// TXA только что создан узким; забываем «широкую» геометрию прошлой
|
||||||
FBufSize,
|
// сессии (RX-половину сбрасывает OpenMainRXChannel).
|
||||||
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;
|
|
||||||
FTXDSPSize := FAudioBufSize;
|
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).
|
// TXA: mic @ FAudioRate → IQ @ FTXSampleRate (фиксированный DUC rate, не зависит от RX).
|
||||||
// out_size = FAudioBufSize * FTXSampleRate/FAudioRate = FTXOutBufSize (4096 @ 192k tx).
|
// out_size = FAudioBufSize * FTXSampleRate/FAudioRate = FTXOutBufSize (4096 @ 192k tx).
|
||||||
// FTXOut уже имеет размер FTXOutBufSize*2.
|
// FTXOut уже имеет размер FTXOutBufSize*2.
|
||||||
@@ -1649,56 +1651,153 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
CloseAnalyzer;
|
CloseAnalyzer;
|
||||||
destroy_anbEXT(RXA_CHAN);
|
CloseMainRXChannel;
|
||||||
destroy_nobEXT(RXA_CHAN);
|
|
||||||
CloseChannel(RXA_CHAN);
|
|
||||||
CloseChannel(TXA_CHAN);
|
CloseChannel(TXA_CHAN);
|
||||||
FInitialized := False;
|
FInitialized := False;
|
||||||
end;
|
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);
|
procedure TWDSPEngine.ChangeSampleRate(NewRate: Integer);
|
||||||
// Меняет samplerate на лету: Close → сброс очереди → пересчёт буферов → Open.
|
// Смена rate главного тракта ХИРУРГИЧЕСКИ: DSP-поток, display-поток,
|
||||||
// Панов не трогает: их rate свой; аккумуляторы сбрасываем (очередь общая,
|
// анализаторы и паны N живут — закрываем/переоткрываем только RXA_CHAN и
|
||||||
// их пакеты умерли вместе с ней), анализаторы пересоздаст OpenAnalyzer.
|
// слайс-каналы пана 0. Пакеты панов идут через общую очередь без пауз:
|
||||||
|
// DSP-поток лишь дропает main-пакеты, пока стоит FMainGated. (Раньше здесь
|
||||||
|
// был полный Close/Open движка — паны замирали на всё время пересборки.)
|
||||||
var
|
var
|
||||||
p: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
if NewRate = FSampleRate then Exit;
|
if NewRate = FSampleRate then Exit;
|
||||||
|
|
||||||
// Останавливаем текущий канал
|
if not FInitialized then
|
||||||
Close;
|
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-поток перестаёт брать main-пакеты (дропает их из очереди);
|
||||||
// Без этого DSP поток тратит секунды на разбор накопившихся пакетов.
|
// барьер Enter/Leave гарантирует, что текущий PushIQItemToDSP
|
||||||
FQueueHead := 0;
|
// (fexchange0 на RXA_CHAN) завершён.
|
||||||
FQueueTail := 0;
|
FMainGated := True;
|
||||||
FRXAccPos := 0;
|
FMainLock.Enter;
|
||||||
for p := 1 to MAX_PANS - 1 do
|
FMainLock.Leave;
|
||||||
if FPans[p].Active then
|
try
|
||||||
begin
|
try
|
||||||
FPans[p].AccPos := 0;
|
// Слайсы пана 0 открыты с InRate = главный rate — закрываем на пересборку.
|
||||||
FPans[p].DispPos := 0;
|
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;
|
end;
|
||||||
|
finally
|
||||||
// Обновляем параметры
|
FMainGated := False;
|
||||||
FSampleRate := NewRate;
|
end;
|
||||||
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);
|
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.SetBeaconDecoder(D: TBeaconDecoder);
|
procedure TWDSPEngine.SetBeaconDecoder(D: TBeaconDecoder);
|
||||||
@@ -2112,9 +2211,13 @@ begin
|
|||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.CloseSliceChannel(Idx: Integer);
|
procedure TWDSPEngine.CloseSliceChannel(Idx: Integer);
|
||||||
|
// dmode=0: fexchange0 слайс-каналов зовётся ТОЛЬКО под FSliceLock (ProcessSlices/
|
||||||
|
// PushPanItemToDSP), и все вызовы CloseSliceChannel — под тем же локом, т.е.
|
||||||
|
// канал заведомо idle. dmode=1 здесь зря ждал флаш ~130мс на канал — при смене
|
||||||
|
// rate главного эта пауза (под локом) замораживала обработку панов.
|
||||||
begin
|
begin
|
||||||
if not FSlices[Idx].Opened then Exit;
|
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_anbEXT(FSlices[Idx].Chan);
|
||||||
destroy_nobEXT(FSlices[Idx].Chan);
|
destroy_nobEXT(FSlices[Idx].Chan);
|
||||||
CloseChannel(FSlices[Idx].Chan);
|
CloseChannel(FSlices[Idx].Chan);
|
||||||
|
|||||||
@@ -512,6 +512,39 @@ ChangeSampleRate).
|
|||||||
(в коде НЕ делалось — сначала экранный тест).
|
(в коде НЕ делалось — сначала экранный тест).
|
||||||
- Персист геометрии — вместе с общим персистом панов.
|
- Персист геометрии — вместе с общим персистом панов.
|
||||||
|
|
||||||
|
**СТАТУС «хирургический ChangeSampleRate» (2026-07-12): ГОТОВО — смоук-тест
|
||||||
|
ALL PASS, юзер подтвердил на экране («всё работает»). Отладочные пробы
|
||||||
|
RATEPROBE удалены.** Наблюдение юзера: смена rate ГЛАВНОГО пана
|
||||||
|
коротко замораживала водопады панов N (обратное — нет). Причина была в движке:
|
||||||
|
`ChangeSampleRate` делал полный Close/Open (стоп DSP-потока, разрушение ВСЕХ
|
||||||
|
анализаторов вкл. пановых, закрытие всех слайс-каналов, сброс общей IQ-очереди).
|
||||||
|
Протокол чист: оба пути шлют одинаковый полный DDC Specific (`RebuildDDCSpecific`).
|
||||||
|
Сделано (WDSPEngine.pas + RadioController.pas):
|
||||||
|
- Гейт `FMainGated` + барьер `FMainLock`: DSP-поток при взведённом гейте дропает
|
||||||
|
main-пакеты (PanId=0) из общей очереди НЕ блокируясь, паны обрабатываются без
|
||||||
|
пауз; двойная проверка гейта (снаружи и под локом) закрывает гонку установки.
|
||||||
|
- `ChangeSampleRate` теперь хирургический: закрывает/переоткрывает ТОЛЬКО
|
||||||
|
RXA_CHAN + слайс-каналы пана 0 (их InRate = главный rate); DSP/display-потоки,
|
||||||
|
TXA, TX-поток, анализаторы и паны не трогаются. RX-анализатор переармируется
|
||||||
|
`ApplyRXAnalyzerSettings` (как при зуме) — история главного сбрасывается
|
||||||
|
(неизбежно), пановые НЕ. Ошибка реконфига → `FInitialized:=False` (контроллер
|
||||||
|
увидит по `Initialized`).
|
||||||
|
- Общий код RXA вынесен: `OpenMainRXChannel` (открытие+реставрация состояния из
|
||||||
|
полей движка, вкл. FShiftHz) / `CloseMainRXChannel` — используются Open/Close/
|
||||||
|
ChangeSampleRate.
|
||||||
|
- Контроллер `SetSampleRate` больше НЕ сбрасывает `FWDSPReady` на время смены —
|
||||||
|
иначе OnDDCIQ переставал кормить паны (это был второй источник паузы).
|
||||||
|
- ★Находка по пробам: `SetChannelState(chan,0,dmode=1)` на ПРОСТАИВАЮЩЕМ канале
|
||||||
|
зря ждёт флаш ~130мс. Каналы при закрытии всегда idle (слайсы — дисциплина
|
||||||
|
FSliceLock, RXA — гейт-барьер) → dmode=1→0 в `CloseSliceChannel` и
|
||||||
|
`CloseMainRXChannel`. Итог: смена rate 331→120мс, макс. пауза аудио слайса
|
||||||
|
пана 137→40мс (~2 блока, на глаз незаметно). Бонус: закрытие слайсов/панов и
|
||||||
|
SetPanRate тоже ускорились на ~130мс/канал.
|
||||||
|
- Смоук-тест scratchpad `ratetest.pas` (синтетический тон, фидер-поток, замер
|
||||||
|
межблочных пауз аудио пан-слайса вокруг смены 384к→192к): ALL PASS — пан без
|
||||||
|
паузы >41мс, main-аудио/слайс пана 0/слайс пана 1 живы и слышат тон после
|
||||||
|
смены. Оба бинарника собираются.
|
||||||
|
|
||||||
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
### 3.6 — Pluto+ (2r2t): второй пан на втором антенном входе
|
||||||
|
|
||||||
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
Опционально, после 3.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
||||||
|
|||||||
Reference in New Issue
Block a user