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