feat(dmr): default tuning step to 12.5 kHz

This commit is contained in:
2026-07-14 20:51:10 +03:00
parent 5cfd585eea
commit 62e663baf5
3 changed files with 60 additions and 22 deletions
+42 -18
View File
@@ -1535,7 +1535,8 @@ begin
begin begin
if FStepDropDown <> nil then FStepDropDown.SetItemIndex(FController.FFMStepIdx); if FStepDropDown <> nil then FStepDropDown.SetItemIndex(FController.FFMStepIdx);
if FWebServer <> nil then FWebServer.FMStepIdx := FController.FFMStepIdx; if FWebServer <> nil then FWebServer.FMStepIdx := FController.FFMStepIdx;
if (FController.FMode = MODE_FM) and (FSpecView <> nil) then if (FController.FMode in [MODE_FM, MODE_DMR]) and
(FSpecView <> nil) then
FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx]; FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx];
end; end;
rfFMStep: rfFMStep:
@@ -3237,7 +3238,7 @@ begin
FSpecView.SpanHz := ViewSpan; FSpecView.SpanHz := ViewSpan;
FSpecView.Mode := FController.FMode; FSpecView.Mode := FController.FMode;
FSpecView.FilterBW := FController.FFilterBW; FSpecView.FilterBW := FController.FFilterBW;
if FController.FMode = MODE_FM then if FController.FMode in [MODE_FM, MODE_DMR] then
FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx] FSpecView.FMGridStepHz := FM_STEP_HZ[FController.FFMStepIdx]
else else
FSpecView.FMGridStepHz := 0; FSpecView.FMGridStepHz := 0;
@@ -4218,11 +4219,26 @@ begin
Exit; Exit;
end; end;
// Non-FM: restore full 2-row height and 5-column layout, hide FM panels // Non-FM: restore full 2-row height and 5-column layout, hide FM panels.
// DMR reuses only the tuning STEP panel; SQL/CTCSS/repeater stay FM-only.
PanelFilter.Height := 74; PanelFilter.Height := 74;
if PanelFMSQ <> nil then PanelFMSQ.Visible := False; if PanelFMSQ <> nil then PanelFMSQ.Visible := False;
if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False; if PanelFMCTCSS <> nil then PanelFMCTCSS.Visible := False;
if PanelFMStep <> nil then begin CloseFMStepPopup; PanelFMStep.Visible := False; end; if PanelFMStep <> nil then
begin
if FController.FMode = MODE_DMR then
begin
StyleButton(BtnFMStep, FController.FFMStepOn);
if FStepDropDown <> nil then
FStepDropDown.SetItemIndex(FController.FFMStepIdx);
PanelFMStep.Visible := True;
end
else
begin
CloseFMStepPopup;
PanelFMStep.Visible := False;
end;
end;
if PanelFMRpt <> nil then PanelFMRpt.Visible := False; if PanelFMRpt <> nil then PanelFMRpt.Visible := False;
if FController.FMode = MODE_WFM then if FController.FMode = MODE_WFM then
@@ -4472,7 +4488,8 @@ var
ViewCenter, ViewSpan: Double; ViewCenter, ViewSpan: Double;
begin begin
if PanelWidth <= 0 then Exit; if PanelWidth <= 0 then Exit;
if (FController.FMode = MODE_FM) and FController.FFMStepOn then if (FController.FMode in [MODE_FM, MODE_DMR]) and
FController.FFMStepOn then
StepHz := FM_STEP_HZ[FController.FFMStepIdx] StepHz := FM_STEP_HZ[FController.FFMStepIdx]
else else
StepHz := 100; StepHz := 100;
@@ -4496,7 +4513,8 @@ begin
if (FWidebandView = nil) or if (FWidebandView = nil) or
(not FWidebandView.TryPixelToFrequency(PixelX, PanelWidth, FreqHz)) then Exit; (not FWidebandView.TryPixelToFrequency(PixelX, PanelWidth, FreqHz)) then Exit;
if (FController.FMode = MODE_FM) and FController.FFMStepOn then if (FController.FMode in [MODE_FM, MODE_DMR]) and
FController.FFMStepOn then
StepHz := FM_STEP_HZ[FController.FFMStepIdx] StepHz := FM_STEP_HZ[FController.FFMStepIdx]
else else
StepHz := 100; StepHz := 100;
@@ -6037,28 +6055,31 @@ end;
procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer); procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer);
// Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог // Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог
// DoSpectrumClick главного: снап к 100 Гц). Цель — активный слайс, если он // DoSpectrumClick главного). Цель — активный слайс, если он живёт в этом
// живёт в этом пане, иначе первый слайс пана; слайсов нет — создать в точке. // пане, иначе первый слайс пана; слайсов нет — создать в точке.
const
StepHz = 100;
var var
VC, VS, LoHz, HiHz: Double; VC, VS, LoHz, HiHz: Double;
Hz: Int64; Hz, StepHz: Int64;
Id: Integer; Id: Integer;
S: TCtrlSlice; S: TCtrlSlice;
begin begin
PanViewWindow(P, VC, VS); PanViewWindow(P, VC, VS);
if (W <= 0) or (VS <= 0) then Exit; if (W <= 0) or (VS <= 0) then Exit;
Hz := Round(VC + (X / W - 0.5) * VS);
Hz := ((Hz + StepHz div 2) div StepHz) * StepHz;
FController.CapturedSpan(LoHz, HiHz, P.PanId);
if Hz < LoHz then Hz := Round(LoHz);
if Hz > HiHz then Hz := Round(HiHz);
if (FActiveSliceId <> 0) and FController.GetSlice(FActiveSliceId, S) and if (FActiveSliceId <> 0) and FController.GetSlice(FActiveSliceId, S) and
(S.PanId = P.PanId) then (S.PanId = P.PanId) then
Id := FActiveSliceId Id := FActiveSliceId
else else
Id := P.FirstSliceId; 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
StepHz := 12500;
Hz := Round(VC + (X / W - 0.5) * VS);
Hz := ((Hz + StepHz div 2) div StepHz) * StepHz;
FController.CapturedSpan(LoHz, HiHz, P.PanId);
if Hz < LoHz then Hz := Round(LoHz);
if Hz > HiHz then Hz := Round(HiHz);
if Id = 0 then if Id = 0 then
begin begin
AddSliceAtFreqPan(P, Hz); AddSliceAtFreqPan(P, Hz);
@@ -7571,7 +7592,9 @@ begin
end; end;
if (SliceId <> 0) and FController.GetSlice(SliceId, Sl) then if (SliceId <> 0) and FController.GetSlice(SliceId, Sl) then
begin begin
if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000 if (Sl.Mode = MODE_DMR) 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 else if ssCtrl in Shift then Step := 1000
else if ssShift in Shift then Step := 10 else if ssShift in Shift then Step := 10
else Step := 100; else Step := 100;
@@ -7593,7 +7616,8 @@ begin
Handled := True; Handled := True;
Exit; Exit;
end; end;
if (FController.FMode = MODE_FM) and FController.FFMStepOn and if (FController.FMode in [MODE_FM, MODE_DMR]) and
FController.FFMStepOn and
not ((ssCtrl in Shift) or (ssShift in Shift)) then not ((ssCtrl in Shift) or (ssShift in Shift)) then
Step := FM_STEP_HZ[FController.FFMStepIdx] Step := FM_STEP_HZ[FController.FFMStepIdx]
else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000 else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000
+14
View File
@@ -2753,6 +2753,11 @@ begin
end; end;
FFMStepOn := B.FMStepOn; FFMStepOn := B.FMStepOn;
FFMStepIdx := EnsureRange(B.FMStepIdx, 0, 3); FFMStepIdx := EnsureRange(B.FMStepIdx, 0, 3);
if FMode = MODE_DMR then
begin
FFMStepOn := True;
FFMStepIdx := 1; // DMR default raster: 12.5 kHz
end;
FFMRptDir := B.FMRptDir; FFMRptDir := B.FMRptDir;
FFMRptOffsetHz := B.FMRptOffsetHz; FFMRptOffsetHz := B.FMRptOffsetHz;
if FFMRptOffsetHz <= 0 then FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); if FFMRptOffsetHz <= 0 then FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA);
@@ -3110,6 +3115,15 @@ end;
procedure TRadioController.SetMode(M: Integer); procedure TRadioController.SetMode(M: Integer);
begin begin
FMode := M; FMode := M;
// DMR channels use a 12.5 kHz raster by default. Keep the ordinary STEP
// controls enabled so the operator can select another raster afterwards.
if FMode = MODE_DMR then
begin
FFMStepOn := True;
FFMStepIdx := 1; // 12.5 kHz
Changed(rfFMStep);
Changed(rfFMStepIdx);
end;
// Entering receive-only DMR on the selected main TX source immediately // Entering receive-only DMR on the selected main TX source immediately
// releases an existing carrier. SetTune(False) also releases MOX. // releases an existing carrier. SetTune(False) also releases MOX.
if ActiveTXMode = MODE_DMR then if ActiveTXMode = MODE_DMR then
+4 -4
View File
@@ -817,7 +817,7 @@ begin
'function drawRuler(){const w=rulerCv.clientWidth,h=rulerCv.clientHeight;rctx.fillStyle="#0c1210";rctx.fillRect(0,0,w,h);' + 'function drawRuler(){const w=rulerCv.clientWidth,h=rulerCv.clientHeight;rctx.fillStyle="#0c1210";rctx.fillRect(0,0,w,h);' +
'const st=localCenter-localSpan/2;' + 'const st=localCenter-localSpan/2;' +
'const isFM=(cur.mode===5),fmStep=FM_STEP_HZ[fmStepIdx];' + 'const isFM=(cur.mode===5||cur.mode===11),fmStep=FM_STEP_HZ[fmStepIdx];' +
'if(isFM&&fmStep>0){' + 'if(isFM&&fmStep>0){' +
'const pxPerStep=w*fmStep/localSpan;' + 'const pxPerStep=w*fmStep/localSpan;' +
'const labelMult=Math.max(1,Math.ceil((rctx.measureText("000.000").width+6)/pxPerStep));' + 'const labelMult=Math.max(1,Math.ceil((rctx.measureText("000.000").width+6)/pxPerStep));' +
@@ -832,7 +832,7 @@ begin
'function drawSpec(bins){const w=specCv.clientWidth,h=specCv.clientHeight;sctx.fillStyle="#050a05";sctx.fillRect(0,0,w,h);' + 'function drawSpec(bins){const w=specCv.clientWidth,h=specCv.clientHeight;sctx.fillStyle="#050a05";sctx.fillRect(0,0,w,h);' +
'for(let i=1;i<5;i++){const y=i*h/5;sctx.strokeStyle="#121e12";sctx.lineWidth=1;sctx.beginPath();sctx.moveTo(0,y);sctx.lineTo(w,y);sctx.stroke();}' + 'for(let i=1;i<5;i++){const y=i*h/5;sctx.strokeStyle="#121e12";sctx.lineWidth=1;sctx.beginPath();sctx.moveTo(0,y);sctx.lineTo(w,y);sctx.stroke();}' +
'if(cur.mode===5&&FM_STEP_HZ[fmStepIdx]>0){' + 'if((cur.mode===5||cur.mode===11)&&FM_STEP_HZ[fmStepIdx]>0){' +
'const fmSt=FM_STEP_HZ[fmStepIdx],st2=localCenter-localSpan/2;' + 'const fmSt=FM_STEP_HZ[fmStepIdx],st2=localCenter-localSpan/2;' +
'const pxSt=w*fmSt/localSpan,gm=Math.max(1,Math.ceil(4/pxSt));' + 'const pxSt=w*fmSt/localSpan,gm=Math.max(1,Math.ceil(4/pxSt));' +
'const fn=Math.ceil(st2/fmSt);' + 'const fn=Math.ceil(st2/fmSt);' +
@@ -875,7 +875,7 @@ begin
'if(smCv.width!==(sw*dpr|0)||smCv.height!==(sh*dpr|0)){smCv.width=sw*dpr|0;smCv.height=sh*dpr|0;mctx.setTransform(dpr,0,0,dpr,0,0);}' + 'if(smCv.width!==(sw*dpr|0)||smCv.height!==(sh*dpr|0)){smCv.width=sw*dpr|0;smCv.height=sh*dpr|0;mctx.setTransform(dpr,0,0,dpr,0,0);}' +
'if(wfBuf.width!==wfCv.clientWidth||wfBuf.height!==wfCv.clientHeight){wfBuf.width=wfCv.clientWidth;wfBuf.height=wfCv.clientHeight;wfBCtx.fillStyle="#000";wfBCtx.fillRect(0,0,wfBuf.width,wfBuf.height);}}' + 'if(wfBuf.width!==wfCv.clientWidth||wfBuf.height!==wfCv.clientHeight){wfBuf.width=wfCv.clientWidth;wfBuf.height=wfCv.clientHeight;wfBCtx.fillStyle="#000";wfBCtx.fillRect(0,0,wfBuf.width,wfBuf.height);}}' +
'function syncStepSel(){const isFM=(cur.mode===5);const sel=q("stepSel");if(!sel)return;' + 'function syncStepSel(){const isFM=(cur.mode===5||cur.mode===11);const sel=q("stepSel");if(!sel)return;' +
'if(isFM&&!inFMStep){lastHFStep=+sel.value||100;sel.innerHTML="";' + 'if(isFM&&!inFMStep){lastHFStep=+sel.value||100;sel.innerHTML="";' +
'FM_STEP_LBL.forEach((l,i)=>{const o=document.createElement("option");o.value=FM_STEP_HZ[i];o.textContent=l;sel.appendChild(o);});' + 'FM_STEP_LBL.forEach((l,i)=>{const o=document.createElement("option");o.value=FM_STEP_HZ[i];o.textContent=l;sel.appendChild(o);});' +
'sel.value=FM_STEP_HZ[fmStepIdx];' + 'sel.value=FM_STEP_HZ[fmStepIdx];' +
@@ -937,7 +937,7 @@ begin
'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' + 'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' +
// клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули. // клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули.
// Спектр остаётся на месте, сервер пришлёт center_hz через ~200ms и всё перецентрируется. // Спектр остаётся на месте, сервер пришлёт center_hz через ~200ms и всё перецентрируется.
'const snap=(cur.mode===5&&inFMStep)?FM_STEP_HZ[fmStepIdx]:100;' + 'const snap=((cur.mode===5||cur.mode===11)&&inFMStep)?FM_STEP_HZ[fmStepIdx]:100;' +
'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/snap)*snap,vfoMin(),vfoMax());' + 'const nf=cl(Math.round(xToHz(e.offsetX,cv.clientWidth)/snap)*snap,vfoMin(),vfoMax());' +
'setActiveVfoHz(nf);' + 'setActiveVfoHz(nf);' +
'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' + 'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +