mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(vfo): кнопка SQL + ползунок FM-squelch в DSP-меню флага (main + слайсы)
В fly-out DSP появляется ряд SQL (тумблер + порог 0..100) только в режиме FM. Squelch работает per-slice: у слайсов свои WDSP-каналы, поэтому добавлены поля/сеттеры FMSQ в TSliceChan/TCtrlSlice, ApplyFMSQToChan и восстановление при переоткрытии канала. Роутинг события по SliceId (0=RXA, иначе слайс). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+19
-1
@@ -524,6 +524,7 @@ type
|
|||||||
procedure UpdateVfoOverlayVisibility; // флаг A виден: панель скрыта ИЛИ есть слайсы B+
|
procedure UpdateVfoOverlayVisibility; // флаг A виден: панель скрыта ИЛИ есть слайсы B+
|
||||||
procedure OnModeFilterSelect(Mode: Integer; FilterBW: Integer);
|
procedure OnModeFilterSelect(Mode: Integer; FilterBW: Integer);
|
||||||
procedure OnVfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
procedure OnVfoOverlayDSPChange(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
||||||
|
procedure OnVfoOverlaySquelchChange(SQLOn: Boolean; SQLLevel: Integer);
|
||||||
procedure OnVfoOverlayAGCChange(AGCMode: Integer);
|
procedure OnVfoOverlayAGCChange(AGCMode: Integer);
|
||||||
procedure OnVfoOverlayVolume(Vol: Integer);
|
procedure OnVfoOverlayVolume(Vol: Integer);
|
||||||
procedure OnVfoOverlayMute(Sender: TObject);
|
procedure OnVfoOverlayMute(Sender: TObject);
|
||||||
@@ -1515,10 +1516,12 @@ begin
|
|||||||
// панели флаг правит себя сам (DoHit), синк не нужен. Только дискретные поля —
|
// панели флаг правит себя сам (DoHit), синк не нужен. Только дискретные поля —
|
||||||
// SetState/SetDSPState безусловно инвалидируют (не звать на потоковых rfVfoA).
|
// SetState/SetDSPState безусловно инвалидируют (не звать на потоковых rfVfoA).
|
||||||
if Assigned(FVfoOverlay) and FVfoOverlay.Visible and (not FPanelHidden) and
|
if Assigned(FVfoOverlay) and FVfoOverlay.Visible and (not FPanelHidden) and
|
||||||
(Field in [rfMode, rfFilter, rfNR, rfNB, rfSNB, rfANF, rfAGCMode]) then
|
(Field in [rfMode, rfFilter, rfNR, rfNB, rfSNB, rfANF, rfAGCMode,
|
||||||
|
rfFMSQ, rfFMSQLevel]) then
|
||||||
begin
|
begin
|
||||||
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
||||||
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
||||||
|
FVfoOverlay.SetSquelchState(FController.FFMSQOn, FController.FFMSQLevel);
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
FSyncingFromController := False;
|
FSyncingFromController := False;
|
||||||
@@ -5128,6 +5131,7 @@ begin
|
|||||||
FVfoOverlay.Height := FVfoOverlay.BaseHeight; // растёт сам при открытии fly-out
|
FVfoOverlay.Height := FVfoOverlay.BaseHeight; // растёт сам при открытии fly-out
|
||||||
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
||||||
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
||||||
|
FVfoOverlay.SetSquelchState(FController.FFMSQOn, FController.FFMSQLevel);
|
||||||
FVfoOverlay.Visible := True;
|
FVfoOverlay.Visible := True;
|
||||||
PushVfoOverlayExtState;
|
PushVfoOverlayExtState;
|
||||||
PushVfoOverlayAudioDevices;
|
PushVfoOverlayAudioDevices;
|
||||||
@@ -5292,6 +5296,7 @@ begin
|
|||||||
begin
|
begin
|
||||||
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
FVfoOverlay.SetState(FController.FMode, FController.FFilterBW, FController.FVfoA, FController.FLastSMeter);
|
||||||
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
FVfoOverlay.SetDSPState(FController.FNRMode, FController.FNBMode, FController.FSNB, FController.FANF, FController.FAGCMode);
|
||||||
|
FVfoOverlay.SetSquelchState(FController.FFMSQOn, FController.FFMSQLevel);
|
||||||
PositionVfoOverlay; // перепозиционируем при смене LSB↔USB
|
PositionVfoOverlay; // перепозиционируем при смене LSB↔USB
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
@@ -5310,6 +5315,17 @@ begin
|
|||||||
FController.SetANF(ANFOn);
|
FController.SetANF(ANFOn);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.OnVfoOverlaySquelchChange(SQLOn: Boolean; SQLLevel: Integer);
|
||||||
|
begin
|
||||||
|
if CurSliceId <> 0 then
|
||||||
|
begin
|
||||||
|
FController.SetSliceFMSquelch(CurSliceId, SQLOn, SQLLevel);
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
|
FController.SetFMSquelchLevel(SQLLevel);
|
||||||
|
FController.SetFMSquelch(SQLOn);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TMainForm.OnVfoOverlayAGCChange(AGCMode: Integer);
|
procedure TMainForm.OnVfoOverlayAGCChange(AGCMode: Integer);
|
||||||
begin
|
begin
|
||||||
if CurSliceId <> 0 then
|
if CurSliceId <> 0 then
|
||||||
@@ -5436,6 +5452,7 @@ procedure TMainForm.WireOverlayEvents(O: TVfoOverlay);
|
|||||||
begin
|
begin
|
||||||
O.OnSelect := OnModeFilterSelect;
|
O.OnSelect := OnModeFilterSelect;
|
||||||
O.OnDSPChange := OnVfoOverlayDSPChange;
|
O.OnDSPChange := OnVfoOverlayDSPChange;
|
||||||
|
O.OnSquelchChange := OnVfoOverlaySquelchChange;
|
||||||
O.OnAGCChange := OnVfoOverlayAGCChange;
|
O.OnAGCChange := OnVfoOverlayAGCChange;
|
||||||
O.OnVolume := OnVfoOverlayVolume;
|
O.OnVolume := OnVfoOverlayVolume;
|
||||||
O.OnMute := OnVfoOverlayMute;
|
O.OnMute := OnVfoOverlayMute;
|
||||||
@@ -5565,6 +5582,7 @@ begin
|
|||||||
end;
|
end;
|
||||||
O.SetState(S.Mode, BW, S.TargetHz, FController.SliceSMeter(SliceId));
|
O.SetState(S.Mode, BW, S.TargetHz, FController.SliceSMeter(SliceId));
|
||||||
O.SetDSPState(0, 0, False, False, FController.FAGCMode);
|
O.SetDSPState(0, 0, False, False, FController.FAGCMode);
|
||||||
|
O.SetSquelchState(S.FMSQOn, S.FMSQLevel);
|
||||||
O.SetExtState(Round(S.Volume * 100), S.Muted, False, False, False);
|
O.SetExtState(Round(S.Volume * 100), S.Muted, False, False, False);
|
||||||
// Список аудио-устройств (общий PA-список) + текущее устройство слайса.
|
// Список аудио-устройств (общий PA-список) + текущее устройство слайса.
|
||||||
if Assigned(FController.FAudioOut) then
|
if Assigned(FController.FAudioOut) then
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ type
|
|||||||
AGC: TWDSPAGCMode;
|
AGC: TWDSPAGCMode;
|
||||||
Volume: Double; // 0..1
|
Volume: Double; // 0..1
|
||||||
Muted: Boolean;
|
Muted: Boolean;
|
||||||
|
FMSQOn: Boolean; // FM-squelch включён (слайс в режиме FM)
|
||||||
|
FMSQLevel: Integer; // порог squelch 0..100
|
||||||
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
DeviceIndex: Integer; // PortAudio-индекс (-1 = default)
|
||||||
DevName: string;
|
DevName: string;
|
||||||
Audio: TAudioOutput; // владеет; свой поток вывода
|
Audio: TAudioOutput; // владеет; свой поток вывода
|
||||||
@@ -529,6 +531,7 @@ type
|
|||||||
procedure SetSliceVolume(Id: Integer; Vol: Double); // 0..1
|
procedure SetSliceVolume(Id: Integer; Vol: Double); // 0..1
|
||||||
procedure SetSliceMute(Id: Integer; Mute: Boolean);
|
procedure SetSliceMute(Id: Integer; Mute: Boolean);
|
||||||
procedure SetSliceDSP(Id, NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
procedure SetSliceDSP(Id, NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean);
|
||||||
|
procedure SetSliceFMSquelch(Id: Integer; On_: Boolean; Level: Integer);
|
||||||
function SliceSMeter(Id: Integer): Double; // dBm, из DSP-потока
|
function SliceSMeter(Id: Integer): Double; // dBm, из DSP-потока
|
||||||
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
procedure SetSliceDevice(Id, DevIndex: Integer; const DevName: string);
|
||||||
function SliceCount: Integer;
|
function SliceCount: Integer;
|
||||||
@@ -1153,6 +1156,8 @@ begin
|
|||||||
FSlices[slot].AGC := AGC;
|
FSlices[slot].AGC := AGC;
|
||||||
FSlices[slot].Volume := Vol;
|
FSlices[slot].Volume := Vol;
|
||||||
FSlices[slot].Muted := False;
|
FSlices[slot].Muted := False;
|
||||||
|
FSlices[slot].FMSQOn := False; // squelch по умолчанию выключен
|
||||||
|
FSlices[slot].FMSQLevel := 40;
|
||||||
FSlices[slot].DeviceIndex := DevIndex;
|
FSlices[slot].DeviceIndex := DevIndex;
|
||||||
FSlices[slot].DevName := DevName;
|
FSlices[slot].DevName := DevName;
|
||||||
FSlices[slot].Audio := AO;
|
FSlices[slot].Audio := AO;
|
||||||
@@ -1247,6 +1252,17 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TRadioController.SetSliceFMSquelch(Id: Integer; On_: Boolean; Level: Integer);
|
||||||
|
var idx: Integer;
|
||||||
|
begin
|
||||||
|
idx := FindSliceIndex(Id);
|
||||||
|
if idx < 0 then Exit;
|
||||||
|
FSlices[idx].FMSQOn := On_;
|
||||||
|
FSlices[idx].FMSQLevel := Level;
|
||||||
|
if Assigned(FDSPEngine) then
|
||||||
|
FDSPEngine.SetSliceFMSquelch(Id, On_, Level);
|
||||||
|
end;
|
||||||
|
|
||||||
function TRadioController.SliceSMeter(Id: Integer): Double;
|
function TRadioController.SliceSMeter(Id: Integer): Double;
|
||||||
begin
|
begin
|
||||||
if Assigned(FDSPEngine) then Result := FDSPEngine.GetSliceSMeter(Id)
|
if Assigned(FDSPEngine) then Result := FDSPEngine.GetSliceSMeter(Id)
|
||||||
|
|||||||
+90
-1
@@ -41,6 +41,7 @@ function SliceColor(L: Char): TColor;
|
|||||||
type
|
type
|
||||||
TModeFilterEvent = procedure(Mode: Integer; FilterBW: Integer) of object;
|
TModeFilterEvent = procedure(Mode: Integer; FilterBW: Integer) of object;
|
||||||
TDSPChangeEvent = procedure(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean) of object;
|
TDSPChangeEvent = procedure(NRMode, NBMode: Integer; SNBOn, ANFOn: Boolean) of object;
|
||||||
|
TSquelchEvent = procedure(SQLOn: Boolean; SQLLevel: Integer) of object;
|
||||||
TAGCChangeEvent = procedure(AGCMode: Integer) of object;
|
TAGCChangeEvent = procedure(AGCMode: Integer) of object;
|
||||||
TVolumeEvent = procedure(Vol: Integer) of object;
|
TVolumeEvent = procedure(Vol: Integer) of object;
|
||||||
TAudioDeviceEvent = procedure(DevIndex: Integer; const DevName: string) of object;
|
TAudioDeviceEvent = procedure(DevIndex: Integer; const DevName: string) of object;
|
||||||
@@ -61,6 +62,9 @@ type
|
|||||||
FANFOn: Boolean;
|
FANFOn: Boolean;
|
||||||
FAGCMode: Integer; // 0=FAST 1=MED 2=SLOW 3=LONG 4=OFF
|
FAGCMode: Integer; // 0=FAST 1=MED 2=SLOW 3=LONG 4=OFF
|
||||||
|
|
||||||
|
FSQLOn: Boolean; // FM-шумоподавитель (squelch) включён
|
||||||
|
FSQLLevel: Integer; // порог squelch 0..100
|
||||||
|
|
||||||
FVolume: Integer; // 0..100
|
FVolume: Integer; // 0..100
|
||||||
FMuted: Boolean;
|
FMuted: Boolean;
|
||||||
FSplit: Boolean;
|
FSplit: Boolean;
|
||||||
@@ -77,9 +81,12 @@ type
|
|||||||
FAudScroll: Integer; // прокрутка списка аудио-устройств (верхний индекс)
|
FAudScroll: Integer; // прокрутка списка аудио-устройств (верхний индекс)
|
||||||
FVolTrack: TRect; // трек громкости (локальные координаты)
|
FVolTrack: TRect; // трек громкости (локальные координаты)
|
||||||
FVolDrag: Boolean;
|
FVolDrag: Boolean;
|
||||||
|
FSqlTrack: TRect; // трек squelch в DSP-панели (лок. координаты)
|
||||||
|
FSqlDrag: Boolean;
|
||||||
|
|
||||||
FOnSelect: TModeFilterEvent;
|
FOnSelect: TModeFilterEvent;
|
||||||
FOnDSPChange: TDSPChangeEvent;
|
FOnDSPChange: TDSPChangeEvent;
|
||||||
|
FOnSquelchChange: TSquelchEvent;
|
||||||
FOnAGCChange: TAGCChangeEvent;
|
FOnAGCChange: TAGCChangeEvent;
|
||||||
FOnVolume: TVolumeEvent;
|
FOnVolume: TVolumeEvent;
|
||||||
FOnMute: TNotifyEvent;
|
FOnMute: TNotifyEvent;
|
||||||
@@ -131,6 +138,8 @@ type
|
|||||||
function ComputeHeight: Integer;
|
function ComputeHeight: Integer;
|
||||||
procedure SetPanel(P: TFlyPanel);
|
procedure SetPanel(P: TFlyPanel);
|
||||||
procedure ApplyVolFromX(LX: Integer);
|
procedure ApplyVolFromX(LX: Integer);
|
||||||
|
procedure ApplySqlFromX(LX: Integer);
|
||||||
|
function IsFM: Boolean;
|
||||||
procedure RequestInvalidate;
|
procedure RequestInvalidate;
|
||||||
procedure RequestMeterInvalidate;
|
procedure RequestMeterInvalidate;
|
||||||
procedure RefreshMeter(C: TCanvas);
|
procedure RefreshMeter(C: TCanvas);
|
||||||
@@ -159,6 +168,7 @@ type
|
|||||||
procedure SetState(AMode, ABW: Integer; AVfoHz, ASMeterDB: Double);
|
procedure SetState(AMode, ABW: Integer; AVfoHz, ASMeterDB: Double);
|
||||||
procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean;
|
procedure SetDSPState(ANRMode, ANBMode: Integer; ASNBOn, AANFOn: Boolean;
|
||||||
AAGCMode: Integer);
|
AAGCMode: Integer);
|
||||||
|
procedure SetSquelchState(AOn: Boolean; ALevel: Integer);
|
||||||
procedure SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
|
procedure SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
|
||||||
procedure SetAudioDevices(Names: TStrings; const Indices: array of Integer;
|
procedure SetAudioDevices(Names: TStrings; const Indices: array of Integer;
|
||||||
const CurName: string);
|
const CurName: string);
|
||||||
@@ -185,6 +195,7 @@ type
|
|||||||
property BaseHeight: Integer read ComputeHeight;
|
property BaseHeight: Integer read ComputeHeight;
|
||||||
property OnSelect: TModeFilterEvent read FOnSelect write FOnSelect;
|
property OnSelect: TModeFilterEvent read FOnSelect write FOnSelect;
|
||||||
property OnDSPChange: TDSPChangeEvent read FOnDSPChange write FOnDSPChange;
|
property OnDSPChange: TDSPChangeEvent read FOnDSPChange write FOnDSPChange;
|
||||||
|
property OnSquelchChange: TSquelchEvent read FOnSquelchChange write FOnSquelchChange;
|
||||||
property OnAGCChange: TAGCChangeEvent read FOnAGCChange write FOnAGCChange;
|
property OnAGCChange: TAGCChangeEvent read FOnAGCChange write FOnAGCChange;
|
||||||
property OnVolume: TVolumeEvent read FOnVolume write FOnVolume;
|
property OnVolume: TVolumeEvent read FOnVolume write FOnVolume;
|
||||||
property OnMute: TNotifyEvent read FOnMute write FOnMute;
|
property OnMute: TNotifyEvent read FOnMute write FOnMute;
|
||||||
@@ -228,6 +239,8 @@ const
|
|||||||
HIT_AUD_DN = -28;
|
HIT_AUD_DN = -28;
|
||||||
HIT_SLICE_SEL = -29; // клик по бейджу буквы (выбрать слайс активным)
|
HIT_SLICE_SEL = -29; // клик по бейджу буквы (выбрать слайс активным)
|
||||||
HIT_CLOSE = -30; // × закрытия слайса (неглавные)
|
HIT_CLOSE = -30; // × закрытия слайса (неглавные)
|
||||||
|
HIT_SQL = -31; // тумблер FM-squelch (в DSP-панели)
|
||||||
|
HIT_SQL_SLIDER = -32; // трек порога FM-squelch
|
||||||
HIT_AGC_PICK_BASE = 100000;
|
HIT_AGC_PICK_BASE = 100000;
|
||||||
HIT_DEV_BASE = 200000;
|
HIT_DEV_BASE = 200000;
|
||||||
|
|
||||||
@@ -402,6 +415,7 @@ begin
|
|||||||
FModeFilter[3] := 500; FModeFilter[4] := 500; FModeFilter[5] := 11000;
|
FModeFilter[3] := 500; FModeFilter[4] := 500; FModeFilter[5] := 11000;
|
||||||
FModeFilter[6] := 8000; FModeFilter[7] := 8000;
|
FModeFilter[6] := 8000; FModeFilter[7] := 8000;
|
||||||
FNRMode := 0; FNBMode := 0; FSNBOn := False; FANFOn := False; FAGCMode := 0;
|
FNRMode := 0; FNBMode := 0; FSNBOn := False; FANFOn := False; FAGCMode := 0;
|
||||||
|
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';
|
||||||
FSliceId := 0; FIsMain := True; // по умолчанию — главный приёмник (слайс A)
|
FSliceId := 0; FIsMain := True; // по умолчанию — главный приёмник (слайс A)
|
||||||
@@ -532,11 +546,19 @@ end;
|
|||||||
|
|
||||||
{ ---- Высота с учётом fly-out ---- }
|
{ ---- Высота с учётом fly-out ---- }
|
||||||
|
|
||||||
|
function TVfoOverlay.IsFM: Boolean;
|
||||||
|
begin
|
||||||
|
Result := FMode = 5; // индекс FM в OVL_MODE_NAMES (= MODE_FM контроллера)
|
||||||
|
end;
|
||||||
|
|
||||||
function TVfoOverlay.PanelContentH: Integer;
|
function TVfoOverlay.PanelContentH: Integer;
|
||||||
begin
|
begin
|
||||||
case FPanel of
|
case FPanel of
|
||||||
fpMode: Result := ROW_H*2 + IGAP + ROW_H + IGAP; // 2 ряда режимов + ряд фильтров
|
fpMode: Result := ROW_H*2 + IGAP + ROW_H + IGAP; // 2 ряда режимов + ряд фильтров
|
||||||
fpDsp: Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
fpDsp: begin
|
||||||
|
Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
||||||
|
if IsFM then Inc(Result, IGAP + ROW_H); // + ряд SQL (только FM)
|
||||||
|
end;
|
||||||
fpAud: Result := Min(Max(1, FDevNames.Count), AUD_MAX_VIS) * AUD_ROW_H;
|
fpAud: Result := Min(Max(1, FDevNames.Count), AUD_MAX_VIS) * AUD_ROW_H;
|
||||||
else Result := 0;
|
else Result := 0;
|
||||||
end;
|
end;
|
||||||
@@ -796,6 +818,34 @@ begin
|
|||||||
RegisterHit(HR, HIT_AGC_PICK_BASE + I);
|
RegisterHit(HR, HIT_AGC_PICK_BASE + I);
|
||||||
BX := BRight;
|
BX := BRight;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// Ряд 3 (только FM): тумблер SQL + ползунок порога squelch
|
||||||
|
if IsFM then
|
||||||
|
begin
|
||||||
|
Inc(Y, ROW_H + IGAP);
|
||||||
|
// кнопка SQL слева (1/4 ширины)
|
||||||
|
BRight := X + RowW div 4;
|
||||||
|
HR := Rect(X, Y, BRight - 2, Y + ROW_H);
|
||||||
|
DrawButton(C, HR, 'SQL', FSQLOn,
|
||||||
|
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_SQL), 7);
|
||||||
|
RegisterHit(HR, HIT_SQL);
|
||||||
|
// ползунок порога справа
|
||||||
|
BX := BRight + 4;
|
||||||
|
FSqlTrack := Rect(BX, Y + ROW_H div 2 - 3, X + RowW, Y + ROW_H div 2 + 3);
|
||||||
|
C.Brush.Color := CLR_BTN_NORM; C.Brush.Style := bsSolid;
|
||||||
|
C.Pen.Color := CLR_BTN_NORM; C.Pen.Style := psSolid;
|
||||||
|
C.RoundRect(FSqlTrack.Left, FSqlTrack.Top, FSqlTrack.Right, FSqlTrack.Bottom, 3, 3);
|
||||||
|
BX := FSqlTrack.Left + Round(FSQLLevel/100.0 * (FSqlTrack.Right - FSqlTrack.Left));
|
||||||
|
if FSQLOn then
|
||||||
|
begin
|
||||||
|
C.Brush.Color := CLR_ACCENT; C.Pen.Color := CLR_ACCENT;
|
||||||
|
C.RoundRect(FSqlTrack.Left, FSqlTrack.Top, BX, FSqlTrack.Bottom, 3, 3);
|
||||||
|
end;
|
||||||
|
C.Brush.Color := CLR_TEXT; C.Pen.Color := CLR_TEXT;
|
||||||
|
C.Ellipse(BX-4, Y + ROW_H div 2 - 5, BX+4, Y + ROW_H div 2 + 5);
|
||||||
|
RegisterHit(Rect(FSqlTrack.Left - 4, Y, FSqlTrack.Right + 4, Y + ROW_H),
|
||||||
|
HIT_SQL_SLIDER);
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TVfoOverlay.DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
|
procedure TVfoOverlay.DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
|
||||||
@@ -1081,6 +1131,9 @@ begin
|
|||||||
HIT_BAR_AUD: begin SetPanel(fpAud); Exit; end;
|
HIT_BAR_AUD: begin SetPanel(fpAud); Exit; end;
|
||||||
HIT_MUTE: begin FMuted := not FMuted; RequestInvalidate;
|
HIT_MUTE: begin FMuted := not FMuted; RequestInvalidate;
|
||||||
if Assigned(FOnMute) then FOnMute(Self); Exit; end;
|
if Assigned(FOnMute) then FOnMute(Self); Exit; end;
|
||||||
|
HIT_SQL: begin FSQLOn := not FSQLOn; RequestInvalidate;
|
||||||
|
if Assigned(FOnSquelchChange) then
|
||||||
|
FOnSquelchChange(FSQLOn, FSQLLevel); Exit; end;
|
||||||
HIT_SPLIT: begin if Assigned(FOnSplit) then FOnSplit(Self); Exit; end;
|
HIT_SPLIT: begin if Assigned(FOnSplit) then FOnSplit(Self); Exit; end;
|
||||||
HIT_TX: begin if Assigned(FOnTxSelect) then FOnTxSelect(Self); Exit; end;
|
HIT_TX: begin if Assigned(FOnTxSelect) then FOnTxSelect(Self); Exit; end;
|
||||||
HIT_SLICE_SEL: begin if Assigned(FOnSliceSelect) then FOnSliceSelect(Self); Exit; end;
|
HIT_SLICE_SEL: begin if Assigned(FOnSliceSelect) then FOnSliceSelect(Self); Exit; end;
|
||||||
@@ -1176,6 +1229,21 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TVfoOverlay.ApplySqlFromX(LX: Integer);
|
||||||
|
var
|
||||||
|
V: Integer;
|
||||||
|
begin
|
||||||
|
if FSqlTrack.Right <= FSqlTrack.Left then Exit;
|
||||||
|
V := Round((LX - FSqlTrack.Left) / (FSqlTrack.Right - FSqlTrack.Left) * 100);
|
||||||
|
if V < 0 then V := 0 else if V > 100 then V := 100;
|
||||||
|
if V <> FSQLLevel then
|
||||||
|
begin
|
||||||
|
FSQLLevel := V;
|
||||||
|
RequestInvalidate;
|
||||||
|
if Assigned(FOnSquelchChange) then FOnSquelchChange(FSQLOn, FSQLLevel);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
{ ---- Внешний ввод (маршрутизируется MainForm поверх спектра) ---- }
|
{ ---- Внешний ввод (маршрутизируется MainForm поверх спектра) ---- }
|
||||||
|
|
||||||
function TVfoOverlay.HandleMouseDown(Button: TMouseButton; X, Y: Integer): Boolean;
|
function TVfoOverlay.HandleMouseDown(Button: TMouseButton; X, Y: Integer): Boolean;
|
||||||
@@ -1200,6 +1268,12 @@ begin
|
|||||||
Result := True;
|
Result := True;
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
|
if FSqlDrag then
|
||||||
|
begin
|
||||||
|
ApplySqlFromX(LX);
|
||||||
|
Result := True;
|
||||||
|
Exit;
|
||||||
|
end;
|
||||||
OldHot := FHotIdx;
|
OldHot := FHotIdx;
|
||||||
FHotIdx := -1;
|
FHotIdx := -1;
|
||||||
if (LX >= 0) and (LY >= 0) and (LX < Width) and (LY < Height) then
|
if (LX >= 0) and (LY >= 0) and (LX < Width) and (LY < Height) then
|
||||||
@@ -1218,6 +1292,7 @@ begin
|
|||||||
Result := False;
|
Result := False;
|
||||||
if not Visible then Exit;
|
if not Visible then Exit;
|
||||||
if FVolDrag then begin FVolDrag := False; Result := True; Exit; end;
|
if FVolDrag then begin FVolDrag := False; Result := True; Exit; end;
|
||||||
|
if FSqlDrag then begin FSqlDrag := False; Result := True; Exit; end;
|
||||||
if (X >= Left) and (Y >= Top) and (X < Left + Width) and (Y < Top + Height) then
|
if (X >= Left) and (Y >= Top) and (X < Left + Width) and (Y < Top + Height) then
|
||||||
Result := True; // клик был по накладке — не пускаем его в спектр
|
Result := True; // клик был по накладке — не пускаем его в спектр
|
||||||
end;
|
end;
|
||||||
@@ -1226,6 +1301,7 @@ function TVfoOverlay.HandleMouseLeave: Boolean;
|
|||||||
begin
|
begin
|
||||||
Result := False;
|
Result := False;
|
||||||
FVolDrag := False;
|
FVolDrag := False;
|
||||||
|
FSqlDrag := False;
|
||||||
if FHotIdx >= 0 then
|
if FHotIdx >= 0 then
|
||||||
begin
|
begin
|
||||||
FHotIdx := -1;
|
FHotIdx := -1;
|
||||||
@@ -1250,6 +1326,11 @@ begin
|
|||||||
FVolDrag := True;
|
FVolDrag := True;
|
||||||
ApplyVolFromX(X);
|
ApplyVolFromX(X);
|
||||||
end
|
end
|
||||||
|
else if HV = HIT_SQL_SLIDER then
|
||||||
|
begin
|
||||||
|
FSqlDrag := True;
|
||||||
|
ApplySqlFromX(X);
|
||||||
|
end
|
||||||
else
|
else
|
||||||
DoHit(HV);
|
DoHit(HV);
|
||||||
Break;
|
Break;
|
||||||
@@ -1298,6 +1379,14 @@ begin
|
|||||||
RequestInvalidate;
|
RequestInvalidate;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TVfoOverlay.SetSquelchState(AOn: Boolean; ALevel: Integer);
|
||||||
|
begin
|
||||||
|
if (FSQLOn = AOn) and ((FSQLLevel = ALevel) or FSqlDrag) then Exit;
|
||||||
|
FSQLOn := AOn;
|
||||||
|
if not FSqlDrag then FSQLLevel := ALevel; // во время drag ведём сами
|
||||||
|
RequestInvalidate;
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TVfoOverlay.SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
|
procedure TVfoOverlay.SetExtState(AVolume: Integer; AMuted, ASplit, ATx, ATxSel: Boolean);
|
||||||
begin
|
begin
|
||||||
if (AVolume = FVolume) and (AMuted = FMuted) and (ASplit = FSplit)
|
if (AVolume = FVolume) and (AMuted = FMuted) and (ASplit = FSplit)
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ type
|
|||||||
NBMode: Integer; // 0=off,1=NB,2=NB2
|
NBMode: Integer; // 0=off,1=NB,2=NB2
|
||||||
SNBOn: Boolean;
|
SNBOn: Boolean;
|
||||||
ANFOn: Boolean;
|
ANFOn: Boolean;
|
||||||
|
FMSQOn: Boolean; // FM-шумоподавитель (squelch) включён
|
||||||
|
FMSQLevel: Integer; // порог squelch 0..100
|
||||||
SMeter: Double; // последнее показание S-метра канала (dBm), пишет DSP-поток
|
SMeter: Double; // последнее показание S-метра канала (dBm), пишет DSP-поток
|
||||||
Opened: Boolean; // WDSP-канал реально открыт (после Open движка)
|
Opened: Boolean; // WDSP-канал реально открыт (после Open движка)
|
||||||
end;
|
end;
|
||||||
@@ -384,6 +386,7 @@ type
|
|||||||
procedure ApplyNBToChan(Chan, NBMode: Integer);
|
procedure ApplyNBToChan(Chan, NBMode: Integer);
|
||||||
procedure ApplySNBToChan(Chan: Integer; On_: Boolean);
|
procedure ApplySNBToChan(Chan: Integer; On_: Boolean);
|
||||||
procedure ApplyANFToChan(Chan: Integer; On_: Boolean);
|
procedure ApplyANFToChan(Chan: Integer; On_: Boolean);
|
||||||
|
procedure ApplyFMSQToChan(Chan: Integer; Enable: Boolean; Level: Integer);
|
||||||
procedure ApplyNoiseFilterState;
|
procedure ApplyNoiseFilterState;
|
||||||
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
procedure ProcessTXBlock; // вызывается из TTXDSPThread
|
||||||
procedure FeedTXDisplaySample(const I, Q: Double);
|
procedure FeedTXDisplaySample(const I, Q: Double);
|
||||||
@@ -421,6 +424,7 @@ type
|
|||||||
procedure SetSliceNBMode(Id, Mode: Integer);
|
procedure SetSliceNBMode(Id, Mode: Integer);
|
||||||
procedure SetSliceSNB(Id: Integer; On_: Boolean);
|
procedure SetSliceSNB(Id: Integer; On_: Boolean);
|
||||||
procedure SetSliceANF(Id: Integer; On_: Boolean);
|
procedure SetSliceANF(Id: Integer; On_: Boolean);
|
||||||
|
procedure SetSliceFMSquelch(Id: Integer; Enable: Boolean; Level: Integer);
|
||||||
function GetSliceSMeter(Id: Integer): Double;
|
function GetSliceSMeter(Id: Integer): Double;
|
||||||
function SliceCount: Integer;
|
function SliceCount: Integer;
|
||||||
function HasSlice(Id: Integer): Boolean;
|
function HasSlice(Id: Integer): Boolean;
|
||||||
@@ -1560,6 +1564,7 @@ begin
|
|||||||
ApplyNBToChan(Chan, FSlices[Idx].NBMode);
|
ApplyNBToChan(Chan, FSlices[Idx].NBMode);
|
||||||
ApplySNBToChan(Chan, FSlices[Idx].SNBOn);
|
ApplySNBToChan(Chan, FSlices[Idx].SNBOn);
|
||||||
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
|
ApplyANFToChan(Chan, FSlices[Idx].ANFOn);
|
||||||
|
ApplyFMSQToChan(Chan, FSlices[Idx].FMSQOn, FSlices[Idx].FMSQLevel);
|
||||||
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
|
SetRXAPanelGain1(Chan, FSlices[Idx].Volume);
|
||||||
SetRXAPanelSelect(Chan, 3);
|
SetRXAPanelSelect(Chan, 3);
|
||||||
SetRXAPanelRun(Chan, 1);
|
SetRXAPanelRun(Chan, 1);
|
||||||
@@ -1797,6 +1802,21 @@ begin
|
|||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TWDSPEngine.SetSliceFMSquelch(Id: Integer; Enable: Boolean; Level: Integer);
|
||||||
|
var idx: Integer;
|
||||||
|
begin
|
||||||
|
FSliceLock.Enter;
|
||||||
|
try
|
||||||
|
idx := FindSliceIdx(Id); if idx < 0 then Exit;
|
||||||
|
FSlices[idx].FMSQOn := Enable;
|
||||||
|
FSlices[idx].FMSQLevel := Level;
|
||||||
|
if FSlices[idx].Opened then
|
||||||
|
ApplyFMSQToChan(FSlices[idx].Chan, Enable, Level);
|
||||||
|
finally
|
||||||
|
FSliceLock.Leave;
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
|
||||||
function TWDSPEngine.GetSliceSMeter(Id: Integer): Double;
|
function TWDSPEngine.GetSliceSMeter(Id: Integer): Double;
|
||||||
var idx: Integer;
|
var idx: Integer;
|
||||||
begin
|
begin
|
||||||
@@ -1985,6 +2005,19 @@ begin
|
|||||||
SetRXAANFRun(Chan, Ord(On_));
|
SetRXAANFRun(Chan, Ord(On_));
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
// FM-squelch на произвольном канале (RXA или слайс). Порог 0..100 → 0.0..0.5,
|
||||||
|
// как в SetFMSquelch для главного приёмника.
|
||||||
|
procedure TWDSPEngine.ApplyFMSQToChan(Chan: Integer; Enable: Boolean; Level: Integer);
|
||||||
|
begin
|
||||||
|
if Enable then
|
||||||
|
begin
|
||||||
|
SetRXAFMSQThreshold(Chan, Level / 200.0);
|
||||||
|
SetRXAFMSQRun(Chan, 1);
|
||||||
|
end
|
||||||
|
else
|
||||||
|
SetRXAFMSQRun(Chan, 0);
|
||||||
|
end;
|
||||||
|
|
||||||
procedure TWDSPEngine.ApplyNRState;
|
procedure TWDSPEngine.ApplyNRState;
|
||||||
begin
|
begin
|
||||||
if not FInitialized then Exit;
|
if not FInitialized then Exit;
|
||||||
|
|||||||
Reference in New Issue
Block a user