mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 19:45:09 +00:00
feat(dmr): show stable status on receiver slices
This commit is contained in:
+41
-1
@@ -34,7 +34,7 @@ uses
|
||||
Classes, SysUtils, Math, Controls, ExtCtrls, StdCtrls, Graphics, Forms,
|
||||
LCLIntf, LCLType,
|
||||
OpenGLContextEx,
|
||||
AppTheme, WDSPEngine, RadioController, VfoOverlay,
|
||||
AppTheme, WDSPEngine, RadioController, DMRDecoder, VfoOverlay,
|
||||
SpectrumView, SpectrumViewOpengl, PanZoomBar, FlatButton;
|
||||
|
||||
type
|
||||
@@ -85,6 +85,7 @@ type
|
||||
FPendingSliceClose: Integer; // SliceId к удалению после мыши (0=нет)
|
||||
FMainFlagPinned: Boolean; // True = левая панель хозяина скрыта
|
||||
FLastSliceMeterMs: QWord; // троттлинг S-метра слайсов (~10 Гц)
|
||||
FLastSliceDMRMs: QWord; // семантический DMR-статус (~4 Гц)
|
||||
FOnWireFlag: TWireFlagEvent;
|
||||
FOnSliceClosed: TSliceClosedEvent;
|
||||
FOnFlagsInvalidate: TNotifyEvent;
|
||||
@@ -282,6 +283,7 @@ type
|
||||
procedure PushAllSliceFlagStates; // все флаги панели (после смены rate и т.п.)
|
||||
procedure PushMainFlagExtState;
|
||||
procedure PushMainFlagAudioDevices;
|
||||
procedure UpdateSliceDMRFlags;
|
||||
// Тик: S-метр/частота главного флага + позиции + троттленный S-метр слайсов.
|
||||
procedure TickFlags;
|
||||
procedure SetFlagsTheme(const T: TAppTheme);
|
||||
@@ -1375,6 +1377,36 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TPanafallPanel.UpdateSliceDMRFlags;
|
||||
var
|
||||
I, Slot: Integer;
|
||||
O: TVfoOverlay;
|
||||
S: TDMRStatus;
|
||||
Txt: string;
|
||||
begin
|
||||
if FController = nil then Exit;
|
||||
for I := 0 to High(FSliceFlags) do
|
||||
begin
|
||||
O := FSliceFlags[I];
|
||||
if (O = nil) or (O.DisplayMode <> MODE_DMR) then Continue;
|
||||
if not FController.GetSliceDMRStatus(O.SliceId, S) or not S.Synced then
|
||||
begin
|
||||
O.UpdateDMRStatus('DMR...', False);
|
||||
Continue;
|
||||
end;
|
||||
|
||||
Txt := 'DMR SYNC';
|
||||
Slot := -1;
|
||||
if S.LinkControlValid then Slot := S.LCSlot
|
||||
else if S.CACHValid then Slot := S.Slot;
|
||||
if Slot in [0, 1] then Txt := 'DMR TS' + IntToStr(Slot + 1);
|
||||
if S.SlotTypeValid then Txt := Txt + ' CC' + IntToStr(S.ColorCode);
|
||||
if S.LinkControlValid and (S.TargetID <> 0) then
|
||||
Txt := Txt + ' TG' + IntToStr(S.TargetID);
|
||||
O.UpdateDMRStatus(Txt, True);
|
||||
end;
|
||||
end;
|
||||
|
||||
// Тик (~частота FSpectrumTimer хозяина): метр/частота главного флага, позиции
|
||||
// всех флагов, троттленный S-метр слайсов.
|
||||
procedure TPanafallPanel.TickFlags;
|
||||
@@ -1400,6 +1432,14 @@ begin
|
||||
if FSliceFlags[I] <> nil then
|
||||
FSliceFlags[I].UpdateSMeter(FController.SliceSMeter(FSliceFlags[I].SliceId));
|
||||
end;
|
||||
// Статус DMR содержит только устойчивые параметры и обновляется отдельно,
|
||||
// заметно реже метра. Сам оверлей инвалидируется лишь после debounce и
|
||||
// только если итоговая надпись действительно изменилась.
|
||||
if GetTickCount64 - FLastSliceDMRMs >= 250 then
|
||||
begin
|
||||
FLastSliceDMRMs := GetTickCount64;
|
||||
UpdateSliceDMRFlags;
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
|
||||
@@ -141,6 +141,7 @@ type
|
||||
constructor Create(AOwner: TRadioController; ASliceId: Integer);
|
||||
destructor Destroy; override;
|
||||
procedure FeedAudio(const Left, Right: array of Single; Count: Integer);
|
||||
procedure GetStatus(out S: TDMRStatus);
|
||||
end;
|
||||
|
||||
// Слайс уровня контроллера: логическая обёртка над WDSP-слайс-каналом
|
||||
@@ -697,6 +698,7 @@ type
|
||||
function GetBeaconScope(out S: TBeaconScope): Boolean;
|
||||
function GetBeaconFrame(out Frame: TBeaconFrame; out RSErrors: Integer): Boolean;
|
||||
function GetDMRStatus(out S: TDMRStatus): Boolean;
|
||||
function GetSliceDMRStatus(SliceId: Integer; out S: TDMRStatus): Boolean;
|
||||
|
||||
// Отображение / приём
|
||||
procedure SetCTun(On_: Boolean);
|
||||
@@ -839,6 +841,12 @@ begin
|
||||
if Assigned(FDecoder) then FDecoder.FeedAudio(Left, Right, Count);
|
||||
end;
|
||||
|
||||
procedure TDMRSliceDecoder.GetStatus(out S: TDMRStatus);
|
||||
begin
|
||||
if Assigned(FDecoder) then FDecoder.GetStatus(S)
|
||||
else FillChar(S, SizeOf(S), 0);
|
||||
end;
|
||||
|
||||
procedure TDMRSliceDecoder.AudioReady(const PCM: array of Single;
|
||||
Count: Integer);
|
||||
begin
|
||||
@@ -2380,6 +2388,18 @@ begin
|
||||
else FillChar(S, SizeOf(S), 0);
|
||||
end;
|
||||
|
||||
function TRadioController.GetSliceDMRStatus(SliceId: Integer;
|
||||
out S: TDMRStatus): Boolean;
|
||||
var
|
||||
Idx: Integer;
|
||||
begin
|
||||
Idx := FindSliceIndex(SliceId);
|
||||
Result := (Idx >= 0) and (FSlices[Idx].Mode = MODE_DMR) and
|
||||
Assigned(FSlices[Idx].DMR);
|
||||
if Result then FSlices[Idx].DMR.GetStatus(S)
|
||||
else FillChar(S, SizeOf(S), 0);
|
||||
end;
|
||||
|
||||
// Кромки полосы под режим/полосу (единый расчёт для главного канала и слайсов).
|
||||
class procedure TRadioController.FilterEdgesFor(Mode, BW: Integer; out Lo, Hi: Integer);
|
||||
var Half: Integer;
|
||||
|
||||
+79
-2
@@ -79,6 +79,11 @@ type
|
||||
FTxSel: Boolean; // этот слайс выбран для передачи (янтарный)
|
||||
FSlice: Char; // буква слайса ('A')
|
||||
FSMeterThreshold: Double; // мин. изменение S-метра (дБ) для перерисовки
|
||||
FDMRText: string; // стабильный краткий статус рядом с полосой
|
||||
FDMRSynced: Boolean;
|
||||
FDMRCandidateText: string; // debounce: не мигать на кратком sync-loss
|
||||
FDMRCandidateSynced: Boolean;
|
||||
FDMRCandidateSince: QWord;
|
||||
|
||||
FDevNames: TStringList; // имена устройств вывода
|
||||
FDevIdx: array of Integer; // соответствующие PortAudio-индексы (-1=default)
|
||||
@@ -182,6 +187,7 @@ type
|
||||
const CurName: string);
|
||||
procedure UpdateSMeter(DB: Double);
|
||||
procedure UpdateVfo(AVfoHz: Double);
|
||||
procedure UpdateDMRStatus(const AText: string; Synced: Boolean);
|
||||
// Тема приложения (цвета кнопок берутся отсюда — как левая панель).
|
||||
procedure SetTheme(const T: TAppTheme);
|
||||
// Привязка к слайсу: id, буква флага, признак главного (слайс A).
|
||||
@@ -450,6 +456,9 @@ begin
|
||||
FSQLOn := False; FSQLLevel := 40; FSqlDrag := False;
|
||||
FVolume := 70; FMuted := False; FSplit := False; FTx := False;
|
||||
FTxSel := True; FSlice := 'A';
|
||||
FDMRText := ''; FDMRSynced := False;
|
||||
FDMRCandidateText := ''; FDMRCandidateSynced := False;
|
||||
FDMRCandidateSince := 0;
|
||||
FSliceId := 0; FIsMain := True; // по умолчанию — главный приёмник (слайс A)
|
||||
FSMeterThreshold := 0.4; // главный флаг — чувствительный метр
|
||||
FPanel := fpNone;
|
||||
@@ -977,8 +986,8 @@ end;
|
||||
|
||||
procedure TVfoOverlay.DrawSelf(C: TCanvas; W, H: Integer);
|
||||
var
|
||||
FreqStr, SStr, BWStr: string;
|
||||
CurY, X, RW, i: Integer;
|
||||
FreqStr, SStr, BWStr, DMRStr: string;
|
||||
CurY, X, RW, i, DMRX, DMRRight: Integer;
|
||||
R: TRect;
|
||||
BarL, BarW, MeterX: Integer;
|
||||
begin
|
||||
@@ -1013,6 +1022,21 @@ begin
|
||||
C.Font.Name := 'Courier New'; C.Font.Size := 8; C.Font.Style := [];
|
||||
C.Font.Color := CLR_DIM; C.Brush.Style := bsClear;
|
||||
C.TextOut(X + 22, CurY + 1, BWStr);
|
||||
// Краткий статус дополнительного DMR-слайса. Он обновляется только при
|
||||
// устойчивом изменении sync/slot/CC/TG, поэтому не заставляет GL-текстуру
|
||||
// перезаливаться на каждом burst.
|
||||
if (not FIsMain) and (FMode = 11) and (FDMRText <> '') then
|
||||
begin
|
||||
DMRStr := FDMRText;
|
||||
DMRX := X + 22 + C.TextWidth(BWStr) + 8;
|
||||
DMRRight := W - PAD - 26 - 6 - 14 - 5; // до кнопок × и TX
|
||||
while (Length(DMRStr) > 1) and
|
||||
(C.TextWidth(DMRStr) > DMRRight - DMRX) do
|
||||
SetLength(DMRStr, Length(DMRStr) - 1);
|
||||
if FDMRSynced then C.Font.Color := CLR_ACCENT
|
||||
else C.Font.Color := CLR_SVAL;
|
||||
C.TextOut(DMRX, CurY + 1, DMRStr);
|
||||
end;
|
||||
// TX badge: a DMR slice is visibly disabled and has no hit target.
|
||||
R := Rect(W - PAD - 26, CurY, W - PAD, CurY + HDR_BADGE_H);
|
||||
if not TxSelectionAllowed then
|
||||
@@ -1405,12 +1429,29 @@ end;
|
||||
{ ---- Обновление состояния из контроллера ---- }
|
||||
|
||||
procedure TVfoOverlay.SetState(AMode, ABW: Integer; AVfoHz, ASMeterDB: Double);
|
||||
var
|
||||
OldMode: Integer;
|
||||
begin
|
||||
OldMode := FMode;
|
||||
FMode := Max(0, Min(OVL_MODE_COUNT - 1, AMode));
|
||||
FFilterBW := ABW;
|
||||
FModeFilter[FMode] := ABW;
|
||||
FVfoHz := AVfoHz;
|
||||
FSMeterDB := ASMeterDB;
|
||||
if FMode <> 11 then
|
||||
begin
|
||||
FDMRText := '';
|
||||
FDMRSynced := False;
|
||||
FDMRCandidateText := '';
|
||||
FDMRCandidateSince := 0;
|
||||
end
|
||||
else if OldMode <> 11 then
|
||||
begin
|
||||
FDMRText := 'DMR...';
|
||||
FDMRSynced := False;
|
||||
FDMRCandidateText := '';
|
||||
FDMRCandidateSince := 0;
|
||||
end;
|
||||
Height := ComputeHeight;
|
||||
RequestInvalidate;
|
||||
end;
|
||||
@@ -1487,4 +1528,40 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.UpdateDMRStatus(const AText: string; Synced: Boolean);
|
||||
var
|
||||
NowTick, HoldMs: QWord;
|
||||
begin
|
||||
if FMode <> 11 then Exit;
|
||||
if (FDMRText = AText) and (FDMRSynced = Synced) then
|
||||
begin
|
||||
FDMRCandidateText := '';
|
||||
FDMRCandidateSince := 0;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
NowTick := GetTickCount64;
|
||||
if (FDMRCandidateText <> AText) or
|
||||
(FDMRCandidateSynced <> Synced) then
|
||||
begin
|
||||
FDMRCandidateText := AText;
|
||||
FDMRCandidateSynced := Synced;
|
||||
FDMRCandidateSince := NowTick;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
// Потерю уже показанной синхронизации держим дольше, чем появление/смену
|
||||
// параметров вызова: короткие замирания не должны мигать в шапке слайса.
|
||||
if FDMRSynced and not Synced then HoldMs := 1200
|
||||
else if FDMRSynced and Synced then HoldMs := 750
|
||||
else HoldMs := 250;
|
||||
if NowTick - FDMRCandidateSince < HoldMs then Exit;
|
||||
|
||||
FDMRText := AText;
|
||||
FDMRSynced := Synced;
|
||||
FDMRCandidateText := '';
|
||||
FDMRCandidateSince := 0;
|
||||
RequestInvalidate;
|
||||
end;
|
||||
|
||||
end.
|
||||
|
||||
Reference in New Issue
Block a user