mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(fm): add universal FM RAW decoder output
This commit is contained in:
+128
-16
@@ -37,6 +37,7 @@ uses
|
||||
WidebandView,
|
||||
StatusBar,
|
||||
PlatformUtils,
|
||||
RadioModes,
|
||||
WinFirewall,
|
||||
BoardUtils, WisdomBuilder, UISync,
|
||||
FlatEdit, FMRepeater,
|
||||
@@ -67,7 +68,6 @@ const
|
||||
WDSP_WISDOM_FILE = 'wdspWisdom00';
|
||||
|
||||
BAND_COUNT = 12; // макс. слотов среди планов (Pluto VHF/UHF = 12, HF = 11)
|
||||
MODE_COUNT = 12;
|
||||
// Полосы/дефолты фильтров (FILT_COUNT, FILT_*_BW, FILT_*_DEF, FILT_FM_*)
|
||||
// перенесены в Settings.pas — нужны TRadioController. Здесь остаются только
|
||||
// UI-подписи кнопок (NAMES).
|
||||
@@ -83,6 +83,8 @@ const
|
||||
('3.0k','2.7k','2.4k','2.1k','1.8k','1.5k','1.2k','900','600','300');
|
||||
FILT_FM_NAMES: array[0..1] of string = ('NFM', 'FM');
|
||||
FILT_WFM_NAMES: array[0..FILT_WFM_COUNT-1] of string = ('200k', '160k', '120k');
|
||||
FILT_RAW_NAMES: array[0..FILT_RAW_COUNT-1] of string =
|
||||
('6.25k', '10k', '12.5k', '20k', '25k');
|
||||
|
||||
// CTCSS tones (38 standard tones, no None — toggle via CTCSS button)
|
||||
// CTCSS_COUNT и CTCSS_TONES перенесены в FMRepeater (нужны TRadioController).
|
||||
@@ -105,8 +107,8 @@ const
|
||||
'160m','80m','60m','40m','30m','20m','17m','15m','12m','10m','6m','');
|
||||
|
||||
MODE_NAMES: array[0..MODE_COUNT-1] of string = (
|
||||
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM','DMR');
|
||||
MODE_ROWS = 3; // 4 колонки; последний ряд неполный
|
||||
'LSB','USB','DSB','CWL','CWU','FM','AM','SAM','DIGU','DIGL','WFM','DMR','FMRAW');
|
||||
MODE_ROWS = 4; // 4 колонки; последний ряд неполный
|
||||
|
||||
SMETER_WIDTH_RATIO = 0.32;
|
||||
SMETER_MARGIN = 3;
|
||||
@@ -299,6 +301,9 @@ type
|
||||
BtnFMSQ: TFlatButton;
|
||||
TrkFMSQ: TFlatSlider;
|
||||
LblFMSQ: TLabel;
|
||||
PanelRawDev: TPanel;
|
||||
TrkRawDev: TFlatSlider;
|
||||
LblRawDev: TLabel;
|
||||
PanelFMCTCSS: TPanel;
|
||||
BtnFMCTCSS: TFlatButton;
|
||||
BtnFMCTCSSTone: TFlatButton;
|
||||
@@ -470,9 +475,11 @@ type
|
||||
procedure Btn2TonClick(Sender: TObject);
|
||||
procedure OnPSPopupChanged(Sender: TObject); // персист PS-параметров
|
||||
procedure UpdatePSButtons; // стиль/доступность PS/2TON
|
||||
procedure UpdateIMDMarkers; // 2TON: маркеры пиков/IMD3 на спектре
|
||||
procedure UpdateFilterButtons;
|
||||
procedure BtnFMSQClick(Sender: TObject);
|
||||
procedure TrkFMSQChange(Sender: TObject);
|
||||
procedure TrkRawDevChange(Sender: TObject);
|
||||
procedure ApplyFMSquelch;
|
||||
procedure BtnFMCTCSSClick(Sender: TObject);
|
||||
procedure BtnFMCTCSSToneClick(Sender: TObject);
|
||||
@@ -930,7 +937,7 @@ begin
|
||||
FController.FVfoA := 14200000;
|
||||
FController.FVfoB := 7100000;
|
||||
FController.FActiveVfo := 0;
|
||||
FController.FMode := 1;
|
||||
FController.FMode := MODE_USB;
|
||||
FController.FFilter := 5;
|
||||
FController.FAGCMode := 1; // MEDIUM
|
||||
FController.FAGCTop := 90; // −90 dBm
|
||||
@@ -1376,6 +1383,7 @@ begin
|
||||
ApplySpecViewGridFromState; // сетка спектра под активный тракт
|
||||
RefreshTxIndicators; // TX-бейджи флагов (какой слайс передаёт)
|
||||
UpdateTXControlAvailability;
|
||||
UpdateIMDMarkers; // 2TON: маркеры IMD на TX-спектре
|
||||
end; // (DUC-очередь сбрасывает SetMOX)
|
||||
rfTuning:
|
||||
StyleButton(BtnTUN, FController.FTuning);
|
||||
@@ -1484,6 +1492,12 @@ begin
|
||||
for i := 0 to FILT_COUNT - 1 do StyleButton(BtnFilter[i], i = FController.FFilter);
|
||||
SyncSpecViewFreq;
|
||||
end;
|
||||
rfFMDeviation:
|
||||
if (TrkRawDev <> nil) and (LblRawDev <> nil) then
|
||||
begin
|
||||
TrkRawDev.Position := Round(FController.FRawFMDeviation) div 50;
|
||||
LblRawDev.Caption := Format('%.2f k', [FController.FRawFMDeviation / 1000.0]);
|
||||
end;
|
||||
rfAGCMode:
|
||||
for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode);
|
||||
rfVolume:
|
||||
@@ -1524,6 +1538,7 @@ begin
|
||||
// подсвечивал → визуал расходился с состоянием.
|
||||
if BtnDUP <> nil then StyleButton(BtnDUP, FController.FDisplayDuplex);
|
||||
UpdateRxMuteButton; // активность RX MUTE зависит от DUP
|
||||
UpdateIMDMarkers; // IMD-маркеры 2TON живут только в DUP
|
||||
// На передаче переключаем источник пикселей и сбрасываем буферы, чтобы
|
||||
// старые TX/RX bin'ы не оставались на следующем кадре.
|
||||
if FController.FTransmitting then
|
||||
@@ -1550,7 +1565,7 @@ begin
|
||||
begin
|
||||
if FStepDropDown <> nil then FStepDropDown.SetItemIndex(FController.FFMStepIdx);
|
||||
if FWebServer <> nil then FWebServer.FMStepIdx := FController.FFMStepIdx;
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR]) and
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR, MODE_FMRAW]) and
|
||||
(FSpecView <> nil) then
|
||||
FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx];
|
||||
end;
|
||||
@@ -2230,6 +2245,35 @@ begin
|
||||
|
||||
Inc(Y, 76);
|
||||
|
||||
// FM RAW discriminator deviation. Slider units are 50 Hz: fine enough for
|
||||
// decoder calibration, while the full 0.5..10 kHz range remains usable.
|
||||
PanelRawDev := TPanel.Create(Self);
|
||||
PanelRawDev.Parent := PanelLeft;
|
||||
PanelRawDev.SetBounds(0, Y, LEFT_W, 50);
|
||||
PanelRawDev.BevelOuter := bvNone;
|
||||
PanelRawDev.Visible := False;
|
||||
MakeLbl(PanelRawDev, 'DEVIATION', 4, 2);
|
||||
TrkRawDev := TFlatSlider.Create(Self);
|
||||
TrkRawDev.Parent := PanelRawDev;
|
||||
TrkRawDev.Left := 5;
|
||||
TrkRawDev.Top := 22;
|
||||
TrkRawDev.Width := LEFT_W - 68;
|
||||
TrkRawDev.Height := 14;
|
||||
TrkRawDev.Min := RAW_FM_DEV_MIN div 50;
|
||||
TrkRawDev.Max := RAW_FM_DEV_MAX div 50;
|
||||
TrkRawDev.Position := Round(FController.FRawFMDeviation) div 50;
|
||||
TrkRawDev.OnChange := TrkRawDevChange;
|
||||
LblRawDev := TLabel.Create(Self);
|
||||
LblRawDev.Parent := PanelRawDev;
|
||||
LblRawDev.Left := LEFT_W - 61;
|
||||
LblRawDev.Top := 20;
|
||||
LblRawDev.Width := 57;
|
||||
LblRawDev.Alignment := taRightJustify;
|
||||
LblRawDev.Caption := Format('%.2f k', [FController.FRawFMDeviation / 1000.0]);
|
||||
LblRawDev.Font.Color := CLR_TEXT;
|
||||
LblRawDev.Font.Name := 'Courier New';
|
||||
LblRawDev.Font.Size := 8;
|
||||
|
||||
// FM Squelch panel (visible only in FM mode, above CTCSS)
|
||||
PanelFMSQ := TPanel.Create(Self);
|
||||
PanelFMSQ.Parent := PanelLeft;
|
||||
@@ -2887,6 +2931,7 @@ begin
|
||||
DP(PanelVfoA); DP(PanelVfoB); DP(PanelVfoButtons);
|
||||
DP(PanelBands); DP(PanelMode); DP(PanelFilter);
|
||||
if PanelFMSQ <> nil then DP(PanelFMSQ);
|
||||
if PanelRawDev <> nil then DP(PanelRawDev);
|
||||
if PanelFMCTCSS <> nil then DP(PanelFMCTCSS);
|
||||
if PanelFMStep <> nil then DP(PanelFMStep);
|
||||
if PanelFMRpt <> nil then DP(PanelFMRpt);
|
||||
@@ -3262,7 +3307,7 @@ begin
|
||||
FSpecView.SpanHz := ViewSpan;
|
||||
FSpecView.Mode := FController.FMode;
|
||||
FSpecView.FilterBW := FController.FFilterBW;
|
||||
if FController.FMode in [MODE_FM, MODE_DMR] then
|
||||
if FController.FMode in [MODE_FM, MODE_DMR, MODE_FMRAW] then
|
||||
FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx]
|
||||
else
|
||||
FSpecView.FMGridStepHz := 0;
|
||||
@@ -3554,7 +3599,7 @@ var Allowed: Boolean;
|
||||
begin
|
||||
if (BtnMOX = nil) or (BtnTUN = nil) or (FController = nil) then Exit;
|
||||
Allowed := FController.FRunning and
|
||||
(FController.ActiveTXMode <> MODE_DMR);
|
||||
not (FController.ActiveTXMode in [MODE_DMR, MODE_FMRAW]);
|
||||
BtnMOX.Enabled := Allowed;
|
||||
BtnTUN.Enabled := Allowed;
|
||||
UpdatePSButtons;
|
||||
@@ -3568,7 +3613,7 @@ var
|
||||
begin
|
||||
if (BtnPS = nil) or (Btn2Ton = nil) or (FController = nil) then Exit;
|
||||
TXAllowed := FController.FRunning and
|
||||
(FController.ActiveTXMode <> MODE_DMR);
|
||||
not (FController.ActiveTXMode in [MODE_DMR, MODE_FMRAW]);
|
||||
PSAllowed := TXAllowed and FController.PSAvailable;
|
||||
BtnPS.Enabled := PSAllowed;
|
||||
Btn2Ton.Enabled := TXAllowed;
|
||||
@@ -3576,6 +3621,30 @@ begin
|
||||
StyleButton(Btn2Ton, FController.FTwoTone);
|
||||
if (FPSPopup <> nil) and TPureSignalPopup(FPSPopup).Visible then
|
||||
TPureSignalPopup(FPSPopup).RefreshStatus;
|
||||
UpdateIMDMarkers;
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateIMDMarkers;
|
||||
// 2TON: маркеры пиков тонов и продуктов IMD3. Только в DUP — дисплей остаётся
|
||||
// на RX и показывает СВОЙ сигнал после PA (настоящие плечи IMD); цифровой
|
||||
// TX-спектр продуктов PA не содержит, там замер бессмыслен. Абсолютные
|
||||
// частоты = TX ± аудио-оффсеты тонов (знак как у генератора: для нижней
|
||||
// боковой тона лежат НИЖЕ несущей). Замер/отрисовка — в SpectrumView.
|
||||
var
|
||||
Sgn: Double;
|
||||
begin
|
||||
if (FSpecView = nil) or (FController = nil) then Exit;
|
||||
if (FController.ActiveTXMode = MODE_LSB) or
|
||||
(FController.ActiveTXMode = MODE_CWL) or
|
||||
(FController.ActiveTXMode = MODE_DIGL) then
|
||||
Sgn := -1.0
|
||||
else
|
||||
Sgn := 1.0;
|
||||
FSpecView.SetIMDMarkers(
|
||||
FController.FTwoTone and FController.FTransmitting
|
||||
and FController.FDisplayDuplex,
|
||||
ActiveTXFreqHz + Sgn * FController.FTXSettings.PSTTFreq1,
|
||||
ActiveTXFreqHz + Sgn * FController.FTXSettings.PSTTFreq2);
|
||||
end;
|
||||
|
||||
procedure TMainForm.UpdateStatusField3;
|
||||
@@ -4268,6 +4337,7 @@ begin
|
||||
EdFMRptOffset.Text := RptFormatOffset(FController.FFMRptOffsetHz);
|
||||
PanelFMRpt.Visible := True;
|
||||
end;
|
||||
if PanelRawDev <> nil then PanelRawDev.Visible := False;
|
||||
RelayoutBelowBands;
|
||||
Exit;
|
||||
end;
|
||||
@@ -4279,7 +4349,7 @@ begin
|
||||
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False;
|
||||
if PanelFMStep <> nil then
|
||||
begin
|
||||
if FController.FMode = MODE_DMR then
|
||||
if FController.FMode in [MODE_DMR, MODE_FMRAW] then
|
||||
begin
|
||||
StyleButton(BtnFMStep, FController.FFMStepOn);
|
||||
if FStepDropDown <> nil then
|
||||
@@ -4293,6 +4363,7 @@ begin
|
||||
end;
|
||||
end;
|
||||
if PanelFMRpt <> nil then PanelFMRpt.Visible := False;
|
||||
if PanelRawDev <> nil then PanelRawDev.Visible := False;
|
||||
|
||||
if FController.FMode = MODE_WFM then
|
||||
begin
|
||||
@@ -4315,6 +4386,33 @@ begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if FController.FMode = MODE_FMRAW then
|
||||
begin
|
||||
CloseCTCSSPopup;
|
||||
if FController.FFilter > FILT_RAW_COUNT - 1 then
|
||||
FController.FFilter := FILT_RAW_DEF;
|
||||
FController.FFilterBW := FILT_RAW_BW[FController.FFilter];
|
||||
for i := 0 to FILT_RAW_COUNT - 1 do
|
||||
begin
|
||||
BtnFilter[i].Caption := FILT_RAW_NAMES[i];
|
||||
BtnFilter[i].Left := LeftPanelButtonLeft(PanelFilter.Width, FILT_RAW_COUNT, i);
|
||||
BtnFilter[i].Width := LeftPanelButtonWidth(PanelFilter.Width, FILT_RAW_COUNT, i);
|
||||
BtnFilter[i].Top := 16;
|
||||
BtnFilter[i].Visible := True;
|
||||
StyleButton(BtnFilter[i], i = FController.FFilter);
|
||||
end;
|
||||
for i := FILT_RAW_COUNT to FILT_COUNT - 1 do BtnFilter[i].Visible := False;
|
||||
PanelFilter.Height := BtnFilter[0].Top + BtnFilter[0].Height + 4;
|
||||
if PanelRawDev <> nil then
|
||||
begin
|
||||
TrkRawDev.Position := Round(FController.FRawFMDeviation) div 50;
|
||||
LblRawDev.Caption := Format('%.2f k', [FController.FRawFMDeviation / 1000.0]);
|
||||
PanelRawDev.Visible := True;
|
||||
end;
|
||||
RelayoutBelowBands;
|
||||
Exit;
|
||||
end;
|
||||
|
||||
if FController.FMode = MODE_DMR then
|
||||
begin
|
||||
CloseCTCSSPopup;
|
||||
@@ -4560,13 +4658,15 @@ end;
|
||||
|
||||
procedure TMainForm.OnPSPopupChanged(Sender: TObject);
|
||||
// Правка PS-параметров в поповере: применение к WDSP делает сам поповер
|
||||
// (ApplyPSSettingsToDSP), здесь — только персист per-device.
|
||||
// (ApplyPSSettingsToDSP), здесь — персист per-device + live-обновление
|
||||
// частот IMD-маркеров (если правили тона при активном 2TON).
|
||||
begin
|
||||
if FController.FDevConnected then
|
||||
begin
|
||||
FController.FSettings.SaveTX(FController.FDevMAC, FController.FTXSettings);
|
||||
FController.FSettings.Save;
|
||||
end;
|
||||
UpdateIMDMarkers;
|
||||
end;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -4587,7 +4687,7 @@ var
|
||||
ViewCenter, ViewSpan: Double;
|
||||
begin
|
||||
if PanelWidth <= 0 then Exit;
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR]) and
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR, MODE_FMRAW]) and
|
||||
FController.FFMStepOn then
|
||||
StepHz := FM_STEP_HZ[FController.FFMStepIdx]
|
||||
else
|
||||
@@ -4612,7 +4712,7 @@ begin
|
||||
if (FWidebandView = nil) or
|
||||
(not FWidebandView.TryPixelToFrequency(PixelX, PanelWidth, FreqHz)) then Exit;
|
||||
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR]) and
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR, MODE_FMRAW]) and
|
||||
FController.FFMStepOn then
|
||||
StepHz := FM_STEP_HZ[FController.FFMStepIdx]
|
||||
else
|
||||
@@ -5094,6 +5194,14 @@ begin
|
||||
SaveCurrentBand;
|
||||
end;
|
||||
|
||||
procedure TMainForm.TrkRawDevChange(Sender: TObject);
|
||||
var Hz: Integer;
|
||||
begin
|
||||
if FSyncingFromController then Exit;
|
||||
Hz := TrkRawDev.Position * 50;
|
||||
FController.SetRawFMDeviation(Hz);
|
||||
end;
|
||||
|
||||
procedure TMainForm.CloseCTCSSPopup;
|
||||
begin
|
||||
if FCTCSSDropDown <> nil then FCTCSSDropDown.ClosePopup;
|
||||
@@ -6183,8 +6291,8 @@ begin
|
||||
Id := P.FirstSliceId;
|
||||
StepHz := 100;
|
||||
if ((Id <> 0) and FController.GetSlice(Id, S) and
|
||||
(S.Mode = MODE_DMR)) or
|
||||
((Id = 0) and (FController.FMode = MODE_DMR)) then
|
||||
(S.Mode in [MODE_DMR, MODE_FMRAW])) or
|
||||
((Id = 0) and (FController.FMode in [MODE_DMR, MODE_FMRAW])) then
|
||||
StepHz := 12500;
|
||||
Hz := Round(VC + (X / W - 0.5) * VS);
|
||||
Hz := ((Hz + StepHz div 2) div StepHz) * StepHz;
|
||||
@@ -7443,6 +7551,10 @@ begin
|
||||
PanelFilter.Top := Y;
|
||||
Y := Y + PanelFilter.Height + 2;
|
||||
end;
|
||||
if (PanelRawDev <> nil) and PanelRawDev.Visible then
|
||||
begin
|
||||
PanelRawDev.Top := Y; Y := Y + PanelRawDev.Height + 2;
|
||||
end;
|
||||
if (PanelFMStep <> nil) and PanelFMStep.Visible then
|
||||
begin
|
||||
PanelFMStep.Top := Y; Y := Y + PanelFMStep.Height + 2;
|
||||
@@ -7719,7 +7831,7 @@ begin
|
||||
end;
|
||||
if (SliceId <> 0) and FController.GetSlice(SliceId, Sl) then
|
||||
begin
|
||||
if (Sl.Mode = MODE_DMR) and
|
||||
if (Sl.Mode in [MODE_DMR, MODE_FMRAW]) and
|
||||
not ((ssCtrl in Shift) or (ssShift in Shift)) then Step := 12500
|
||||
else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000
|
||||
else if ssCtrl in Shift then Step := 1000
|
||||
@@ -7743,7 +7855,7 @@ begin
|
||||
Handled := True;
|
||||
Exit;
|
||||
end;
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR]) and
|
||||
if (FController.FMode in [MODE_FM, MODE_DMR, MODE_FMRAW]) and
|
||||
FController.FFMStepOn and
|
||||
not ((ssCtrl in Shift) or (ssShift in Shift)) then
|
||||
Step := FM_STEP_HZ[FController.FFMStepIdx]
|
||||
|
||||
Reference in New Issue
Block a user