From 62e663baf554d6e2aa8842d3f4e2e1144d6e5587 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 14 Jul 2026 20:51:10 +0300 Subject: [PATCH] feat(dmr): default tuning step to 12.5 kHz --- MainForm.pas | 60 +++++++++++++++++++++++++++++++-------------- RadioController.pas | 14 +++++++++++ WebPageHtml.pas | 8 +++--- 3 files changed, 60 insertions(+), 22 deletions(-) diff --git a/MainForm.pas b/MainForm.pas index 489353c..2414b2e 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1535,7 +1535,8 @@ begin begin if FStepDropDown <> nil then FStepDropDown.SetItemIndex(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]; end; rfFMStep: @@ -3237,7 +3238,7 @@ begin FSpecView.SpanHz := ViewSpan; FSpecView.Mode := FController.FMode; 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] else FSpecView.FMGridStepHz := 0; @@ -4218,11 +4219,26 @@ begin Exit; 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; if PanelFMSQ <> nil then PanelFMSQ.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 FController.FMode = MODE_WFM then @@ -4472,7 +4488,8 @@ var ViewCenter, ViewSpan: Double; begin 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] else StepHz := 100; @@ -4496,7 +4513,8 @@ begin if (FWidebandView = nil) or (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] else StepHz := 100; @@ -6037,28 +6055,31 @@ end; procedure TMainForm.PanClickTune(P: TPanafallPanel; X, W: Integer); // Клик по фону доп. пана = перестроить его слайс на частоту клика (аналог -// DoSpectrumClick главного: снап к 100 Гц). Цель — активный слайс, если он -// живёт в этом пане, иначе первый слайс пана; слайсов нет — создать в точке. -const - StepHz = 100; +// DoSpectrumClick главного). Цель — активный слайс, если он живёт в этом +// пане, иначе первый слайс пана; слайсов нет — создать в точке. var VC, VS, LoHz, HiHz: Double; - Hz: Int64; + Hz, StepHz: Int64; Id: Integer; S: TCtrlSlice; begin PanViewWindow(P, VC, VS); 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 (S.PanId = P.PanId) then Id := FActiveSliceId else 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 begin AddSliceAtFreqPan(P, Hz); @@ -7571,7 +7592,9 @@ begin end; if (SliceId <> 0) and FController.GetSlice(SliceId, Sl) then 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 ssShift in Shift then Step := 10 else Step := 100; @@ -7593,7 +7616,8 @@ begin Handled := True; Exit; 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 Step := FM_STEP_HZ[FController.FFMStepIdx] else if (ssCtrl in Shift) and (ssShift in Shift) then Step := 1000000 diff --git a/RadioController.pas b/RadioController.pas index 9ff84c8..a3fcd7a 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -2753,6 +2753,11 @@ begin end; FFMStepOn := B.FMStepOn; 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; FFMRptOffsetHz := B.FMRptOffsetHz; if FFMRptOffsetHz <= 0 then FFMRptOffsetHz := RptDefaultOffsetHz(B.VfoA); @@ -3110,6 +3115,15 @@ end; procedure TRadioController.SetMode(M: Integer); begin 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 // releases an existing carrier. SetTune(False) also releases MOX. if ActiveTXMode = MODE_DMR then diff --git a/WebPageHtml.pas b/WebPageHtml.pas index 66d34b5..900bca1 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -817,7 +817,7 @@ begin 'function drawRuler(){const w=rulerCv.clientWidth,h=rulerCv.clientHeight;rctx.fillStyle="#0c1210";rctx.fillRect(0,0,w,h);' + '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){' + 'const pxPerStep=w*fmStep/localSpan;' + '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);' + '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 pxSt=w*fmSt/localSpan,gm=Math.max(1,Math.ceil(4/pxSt));' + '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(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="";' + '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];' + @@ -937,7 +937,7 @@ begin 'if(dragging&&Math.abs(e.offsetX-dragX0)<4){' + // клик — не трогаем localCenter: фильтр встаёт точно туда куда кликнули. // Спектр остаётся на месте, сервер пришлёт 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());' + 'setActiveVfoHz(nf);' + 'if(activeVfo==="A")cur.vfo_a_hz=nf;else cur.vfo_b_hz=nf;' +