fix(dmr): normalize voice audio and lock bandwidth

This commit is contained in:
2026-07-14 20:14:50 +03:00
parent bac0b7612e
commit 9755378586
5 changed files with 27 additions and 15 deletions
+10 -1
View File
@@ -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;
+4 -8
View File
@@ -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;
+2
View File
@@ -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);
+7 -3
View File
@@ -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);
+4 -3
View File
@@ -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");' +