diff --git a/DMRDecoder.pas b/DMRDecoder.pas index 010e81a..0190ca7 100644 --- a/DMRDecoder.pas +++ b/DMRDecoder.pas @@ -187,6 +187,9 @@ const DMR_SYNC_MASK = LongWord($00FFFFFF); DMR_SYNC_HOLD_MS = 500; DMR_SYNC_MAX_ERRORS = 2; + // mbelib's floating-point API retains the signed 16-bit PCM scale. EWSDR's + // audio outputs use normalized floating point, where full scale is +/-1.0. + DMR_PCM_SCALE = 1.0 / 32768.0; DMR_SUPERFRAME_SAMPLES = DMR_INPUT_RATE * 360 div 1000; DMR_SYNC_CADENCE_TOLERANCE = DMR_SAMPLES_PER_SYM * 3; @@ -771,7 +774,7 @@ var Frames: TDMRAMBEFrames; PCM: array[0..DMR_PCM_SAMPLES - 1] of Single; MbeResult: TDMRMbeResult; - Slot, FrameIndex, RC: Integer; + Slot, FrameIndex, SampleIndex, RC: Integer; NowTick: QWord; begin if (FMbe = nil) or (FLastSyncTick = 0) or @@ -818,6 +821,12 @@ begin end; Inc(FVoiceFrameCount); if MbeResult.ErrorsTotal > 0 then Inc(FVoiceErrorCount, MbeResult.ErrorsTotal); + for SampleIndex := 0 to High(PCM) do + begin + PCM[SampleIndex] := PCM[SampleIndex] * DMR_PCM_SCALE; + if PCM[SampleIndex] > 1.0 then PCM[SampleIndex] := 1.0 + else if PCM[SampleIndex] < -1.0 then PCM[SampleIndex] := -1.0; + end; if Assigned(FOnAudio) then FOnAudio(PCM, Length(PCM)); end; end; diff --git a/MainForm.pas b/MainForm.pas index a04d557..d3e95cc 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -4155,6 +4155,9 @@ var Names: array[0..FILT_COUNT-1] of string; DefIdx: Integer; begin + // DMR has a mandatory 12.5 kHz channel. There is nothing for the operator + // to choose, so hide the whole filter selector and restore it on exit. + PanelFilter.Visible := FController.FMode <> MODE_DMR; if FController.FMode = MODE_FM then begin // FM: show only NFM and WFM buttons, resize them to fill the row @@ -4236,14 +4239,7 @@ begin FController.FFilter := 0; FController.FFilterBW := FILT_DMR_BW; FController.FFMDeviation := DMR_FM_DEV; - BtnFilter[0].Caption := '12.5k'; - BtnFilter[0].Left := LeftPanelButtonLeft(PanelFilter.Width, 1, 0); - BtnFilter[0].Width := LeftPanelButtonWidth(PanelFilter.Width, 1, 0); - BtnFilter[0].Top := 16; - BtnFilter[0].Visible := True; - StyleButton(BtnFilter[0], True); - for i := 1 to FILT_COUNT - 1 do BtnFilter[i].Visible := False; - PanelFilter.Height := BtnFilter[0].Top + BtnFilter[0].Height + 4; + for i := 0 to FILT_COUNT - 1 do BtnFilter[i].Visible := False; RelayoutBelowBands; Exit; end; diff --git a/RadioController.pas b/RadioController.pas index aa60f32..0c15d4c 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -2423,6 +2423,8 @@ end; procedure TRadioController.SetSliceModeBW(Id, Mode, BW: Integer); var Lo, Hi: Integer; begin + // DMR has one fixed RF channel width; callers select only the mode. + if Mode = MODE_DMR then BW := FILT_DMR_BW; FilterEdgesFor(Mode, BW, Lo, Hi); SetSliceMode(Id, Mode); SetSliceFilter(Id, Lo, Hi); diff --git a/VfoOverlay.pas b/VfoOverlay.pas index 151be77..85b9598 100644 --- a/VfoOverlay.pas +++ b/VfoOverlay.pas @@ -418,7 +418,7 @@ begin case ModeIdx of 5: Result := 2; // FM: NFM/FM 10: Result := 3; // WFM: 200K/160K/120K - 11: Result := 1; // DMR: fixed 12.5 kHz channel + 11: Result := 0; // DMR: fixed 12.5 kHz, no redundant selector else Result := 6; end; end; @@ -579,7 +579,10 @@ end; function TVfoOverlay.PanelContentH: Integer; begin case FPanel of - fpMode: Result := OVL_MODE_ROWS*(ROW_H + IGAP) + ROW_H; // ряды режимов + ряд фильтров + fpMode: begin + Result := OVL_MODE_ROWS*(ROW_H + IGAP); + if GetFilterCountForMode(FMode) > 0 then Inc(Result, ROW_H); + end; fpDsp: begin Result := ROW_H + IGAP + ROW_H; // toggles + AGC if IsFM then Inc(Result, IGAP + ROW_H); // + ряд SQL (только FM) @@ -1221,7 +1224,8 @@ begin begin FModeFilter[FMode] := FFilterBW; FMode := NewMode; - FFilterBW := FModeFilter[FMode]; + if FMode = 11 then FFilterBW := 12500 + else FFilterBW := FModeFilter[FMode]; Height := ComputeHeight; // число фильтров могло смениться (FM=2, WFM=3) RequestInvalidate; if Assigned(FOnSelect) then FOnSelect(FMode, FFilterBW); diff --git a/WebPageHtml.pas b/WebPageHtml.pas index 74ad1ba..66d34b5 100644 --- a/WebPageHtml.pas +++ b/WebPageHtml.pas @@ -306,9 +306,9 @@ begin 'function modGrp(){return q("modGrpSel").value||"ssb";}' + 'function modTglTarget(){const grp=GRP_MAP[modGrp()];const m=cur.mode||0;return(m===grp[0])?grp[1]:grp[0];}' + 'function updModTglLabel(){const b=q("modTglBtn");if(!b)return;const grp=GRP_MAP[modGrp()];const m=cur.mode||0;const show=(m===grp[0]||m===grp[1])?m:grp[0];b.textContent=MODE_N[show];}' + - 'function syncModGrp(m){const g=MODE_TO_GRP[m]||"ssb";const s=q("modGrpSel");if(s&&s.value!==g)s.value=g;updModTglLabel();}' + - 'q("modGrpSel").onchange=function(){const grp=GRP_MAP[modGrp()];const defaultMode=grp[0];cur.mode=defaultMode;updModTglLabel();pend("mode");ws2({cmd:"mode",mode:defaultMode});};' + - 'q("modTglBtn").onclick=function(){audioKick();const next=modTglTarget();cur.mode=next;updModTglLabel();pend("mode");ws2({cmd:"mode",mode:next});};' + + 'function syncModGrp(m){const g=MODE_TO_GRP[m]||"ssb";const s=q("modGrpSel");if(s&&s.value!==g)s.value=g;const f=q("fnpBtn");if(f)f.style.display=(m===11)?"none":"";updModTglLabel();}' + + 'q("modGrpSel").onchange=function(){const grp=GRP_MAP[modGrp()];const defaultMode=grp[0];cur.mode=defaultMode;syncModGrp(defaultMode);pend("mode");ws2({cmd:"mode",mode:defaultMode});};' + + 'q("modTglBtn").onclick=function(){audioKick();const next=modTglTarget();cur.mode=next;syncModGrp(next);pend("mode");ws2({cmd:"mode",mode:next});};' + 'q("agcSel").onchange=function(e){pend("agc");ws2({cmd:"agc",mode:+e.target.value});};' + 'q("attSel").onchange=function(e){pend("attn");ws2({cmd:"attn",idx:+e.target.value});};' + @@ -334,6 +334,7 @@ begin 'function closeFnp(){if(fnpOv){fnpOv.remove();fnpOv=null;}document.removeEventListener("click",fnpOutside);}' + 'function fnpOutside(e){if(fnpOv&&!fnpOv.contains(e.target)&&e.target!==q("fnpBtn"))closeFnp();}' + 'function showFnp(){' + + 'if((cur.mode|0)===11)return;' + 'if(fnpOv){closeFnp();return;}' + 'var g=fnpGrp(),ns=FNP_N[g],bws=FNP_BW[g],cb=cur.filter_bw||2700;' + 'fnpOv=document.createElement("div");' +