mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +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;
|
||||
FBandCache[FCurrentBand].SpanHz := FSpanHz; // для обратной совместимости
|
||||
|
||||
// 1. Останавливаем WDSP и меняем rate (Close + FlushQueue + Open).
|
||||
// 1. Меняем rate главного тракта. Хирургически: движок гейтит и дропает
|
||||
// только main-пакеты, DSP-поток/анализаторы/паны N живут без пауз —
|
||||
// поэтому FWDSPReady НЕ сбрасываем (иначе OnDDCIQ перестал бы кормить
|
||||
// паны на всё время пересборки и их водопады замирали).
|
||||
if FWDSPReady and Assigned(FDSPEngine) then
|
||||
begin
|
||||
FWDSPReady := False;
|
||||
FDSPEngine.ChangeSampleRate(Hz);
|
||||
end;
|
||||
|
||||
// 2. Новый rate трансиверу (после остановки DSP — пакеты сразу с верным rate).
|
||||
// 2. Новый rate трансиверу. Короткий хвост пакетов старого rate, дошедших
|
||||
// до переключения радио, попадает в новый канал — транзиент в единицы мс
|
||||
// (то же окно было и при старой схеме: FWDSPReady поднимался до того, как
|
||||
// радио реально применяло DDC Specific).
|
||||
if Assigned(FNetwork) and FNetwork.Connected then
|
||||
FNetwork.ConfigureDDCs(1, Hz div 1000, FMainADCSrc,
|
||||
FDitherEnabled, FRandomEnabled);
|
||||
|
||||
+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);
|
||||
|
||||
@@ -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.0-3.3. Обычного Pluto НЕ касается (там один RX разведён).
|
||||
|
||||
Reference in New Issue
Block a user