diff --git a/MainForm.pas b/MainForm.pas index 9435fbd..6d560dc 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1313,7 +1313,7 @@ begin rfAGCMode: for i := 0 to 4 do StyleButton(BtnAGCMode[i], i = FController.FAGCMode); rfVolume: - TrkVolume.Position := FController.FVolume; + TrkVolume.Position := FController.ActiveVolume; // RX или TX-монитор rfRxGain: UpdateRxGainUI; rfDrive: @@ -1457,6 +1457,7 @@ begin RebuildXvtrButtons; PushXvtrToWeb; TrkDrive.Position := FController.FDrivePercent; // слайдер ← drive% + TrkVolume.Position := FController.ActiveVolume; // слайдер ← загруженная громкость (был баг: не синхронизировался) UpdateDriveLabel; // переключаем единицу %/дБ под бэкенд if Assigned(FSampleRateOverlay) then FSampleRateOverlay.SetCurrentRate(FController.FSampleRate); @@ -5372,7 +5373,7 @@ procedure TMainForm.PushVfoOverlayExtState; begin if not (Assigned(FVfoOverlay) and FVfoOverlay.Visible) then Exit; // TxSel: этот слайс выбран для передачи. Пока слайс один → всегда True. - FVfoOverlay.SetExtState(FController.FVolume, FController.FMuted, + FVfoOverlay.SetExtState(FController.ActiveVolume, FController.FMuted, FController.FSplitTxB, FController.FTransmitting, True); end; diff --git a/RadioController.pas b/RadioController.pas index 7815a87..9d3c4a9 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -237,7 +237,8 @@ type FBeaconDecInvert: Boolean; // зеркальный baseband (QO-100/LNB) — деф. True // ---- Аудио / TX ---- - FVolume: Integer; + FVolume: Integer; // громкость RX + FTxMonVolume: Integer; // громкость self-monitor RX-аудио на TX (RX MUTE off, DUP) FMuted: Boolean; FDrivePercent: Integer; // драйв 0..100 % (UI-уровень, источник истины) FDriveLevel: Byte; // вычисляемый hardware-байт (CalcDriveByte) @@ -495,6 +496,15 @@ type // Громкость / mute / drive / аттенюатор procedure SetVolume(V: Integer); procedure VolumeBy(Delta: Integer); + // True — сейчас звучит self-monitor даунлинка на передаче (TX + DUP + RX MUTE + // off). В этом контексте SetVolume/слайдер правят FTxMonVolume, иначе FVolume. + function MonitoringTX: Boolean; + // Активная громкость под текущий контекст (RX или TX-монитор) — то, что видит + // слайдер (rfVolume) и получает движок. + function ActiveVolume: Integer; + // Применить активную громкость к движку + синхронизировать слайдер (rfVolume). + // Зовётся на T/R-переходах и смене RX MUTE / DUP. + procedure ApplyActiveVolume; procedure SetMute(On_: Boolean); procedure ToggleMute; procedure SetDrive(V: Integer); @@ -694,7 +704,7 @@ begin FAGCMode := 1; FAGCTop := 90; FRxGainMode := 2; FRxGainDb := 40; // Pluto: slow_attack по умолчанию FRxMuteOnTx := True; // QO-100 self-monitor выкл по умолчанию (RX заглушён на TX) - FVolume := 70; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0; + FVolume := 70; FTxMonVolume := 40; FDrivePercent := 50; FDriveLevel := 0; FAtten := 0; FCenterFreq := FVfoA; FSpanHz := 192000; FSampleRate := 192000; FNextSliceId := 1; // мультислайсы: генератор id FillChar(FSlices, SizeOf(FSlices), 0); @@ -1881,6 +1891,7 @@ begin Result := FLoadedGlobal; // несёт не-радио поля (тема/FPS/digits/CAT) round-trip Result.Volume := FVolume; + Result.TxMonVolume := FTxMonVolume; Result.DriveLevel := FDrivePercent; Result.PAMaxPower := FPAMaxPower; Result.PlutoTxMaxAttDb := FPlutoTxMaxAttDb; @@ -2470,16 +2481,37 @@ end; procedure TRadioController.RxGainBy(Delta: Integer); begin SetRxGain(FRxGainDb + Delta); end; +function TRadioController.MonitoringTX: Boolean; +begin + // Звук даунлинка на передаче идёт только в full-duplex (DUP) при выключенном + // RX MUTE — тот же гейт, что в движке (FKeepRXDuringTX and FMonitorRXDuringTX). + Result := FTransmitting and FDisplayDuplex and (not FRxMuteOnTx); +end; + +function TRadioController.ActiveVolume: Integer; +begin + if MonitoringTX then Result := FTxMonVolume else Result := FVolume; +end; + +procedure TRadioController.ApplyActiveVolume; +begin + if FWDSPReady and Assigned(FDSPEngine) then + FDSPEngine.SetVolume(ActiveVolume / 100.0); + Changed(rfVolume); // слайдер/оверлей ← ActiveVolume +end; + procedure TRadioController.SetVolume(V: Integer); begin if V < 0 then V := 0 else if V > 100 then V := 100; - FVolume := V; - if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetVolume(FVolume / 100.0); + // Правим ту громкость, что сейчас активна: на TX-мониторе — FTxMonVolume, + // иначе FVolume. Обе персистятся; слайдер редактирует активную. + if MonitoringTX then FTxMonVolume := V else FVolume := V; + if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.SetVolume(ActiveVolume / 100.0); Changed(rfVolume); end; procedure TRadioController.VolumeBy(Delta: Integer); -begin SetVolume(FVolume + Delta); end; +begin SetVolume(ActiveVolume + Delta); end; procedure TRadioController.SetMute(On_: Boolean); begin @@ -2906,6 +2938,7 @@ begin // но здесь — чтобы тумблер DUP действовал и в момент передачи. if FWDSPReady and Assigned(FDSPEngine) then FDSPEngine.KeepRXDuringTX := FDisplayDuplex; + ApplyActiveVolume; // смена DUP на передаче может включить/выключить TX-монитор Changed(rfDuplex); end; @@ -2921,6 +2954,7 @@ procedure TRadioController.SetRxMuteOnTx(On_: Boolean); begin FRxMuteOnTx := On_; ApplyRxMuteToEngine; + ApplyActiveVolume; // на передаче тумблер монитора меняет активную громкость Changed(rfRxMuteOnTx); end; @@ -3150,6 +3184,7 @@ begin FNetwork.SendFullHP; // 2й пакет: Alex bit27=0 (T/R relay RX) end; ApplyPlutoTxAtten; // Pluto: TX-мощность по drive на передаче, OFF на приёме + ApplyActiveVolume; // T/R: переключаем громкость RX ↔ TX-монитор (если monitoring) Changed(rfTransmitting); end; @@ -3502,6 +3537,7 @@ begin if FTXSettings.TXSpecGridStep > 0 then FTXSpecGridStep := FTXSettings.TXSpecGridStep; FVolume := FLoadedGlobal.Volume; + FTxMonVolume := EnsureRange(FLoadedGlobal.TxMonVolume, 0, 100); FActiveVfo := EnsureRange(FLoadedGlobal.ActiveVfo, 0, 1); // битый конфиг не должен дать невалидный VFO FPAMaxPower := FLoadedGlobal.PAMaxPower; FPlutoTxMaxAttDb := EnsureRange(FLoadedGlobal.PlutoTxMaxAttDb, -50.0, 0.0); diff --git a/Settings.pas b/Settings.pas index 19378cf..261f500 100644 --- a/Settings.pas +++ b/Settings.pas @@ -219,6 +219,7 @@ type TGlobalSettings = record Volume: Integer; + TxMonVolume: Integer; // громкость self-monitor RX-аудио на TX (RX MUTE off) DriveLevel: Integer; ActiveVfo: Integer; NRMode: Integer; @@ -424,7 +425,7 @@ class procedure TSettingsManager.DefaultGlobal(out G: TGlobalSettings); var i: Integer; begin FillChar(G, SizeOf(G), 0); - G.Volume := 70; G.DriveLevel := 50; G.ActiveVfo := 0; + G.Volume := 70; G.TxMonVolume := 40; G.DriveLevel := 50; G.ActiveVfo := 0; G.NRMode := 0; G.NBMode := 0; G.SNBEnabled := False; G.ANFEnabled := False; G.AGCSlope := 0; G.AGCHangThreshold := 100; G.LastBand := 5; G.LastXvtr := -1; @@ -623,6 +624,7 @@ begin GObj := GetGlobalObj(DevObj); G.Volume := JI(GObj,'volume',70); + G.TxMonVolume := JI(GObj,'tx_mon_volume',40); G.DriveLevel := JI(GObj,'drive_level',50); G.ActiveVfo := JI(GObj,'active_vfo',0); G.NRMode := JI(GObj,'nr_mode',Ord(JB(GObj,'nr_enabled',False))); @@ -759,7 +761,7 @@ procedure TSettingsManager.SaveGlobal(const MAC: array of Byte; var O: TJSONObject; i: Integer; begin O := GetGlobalObj(GetDevObj(MacToStr(MAC))); - JW(O,'volume',G.Volume); JW(O,'drive_level',G.DriveLevel); + JW(O,'volume',G.Volume); JW(O,'tx_mon_volume',G.TxMonVolume); JW(O,'drive_level',G.DriveLevel); JW(O,'active_vfo',G.ActiveVfo); JW(O,'nr_mode',G.NRMode); JW(O,'nb_mode',G.NBMode); JW(O,'nr_enabled',G.NRMode <> 0); JW(O,'nb_enabled',G.NBMode <> 0); diff --git a/WebAdapter.pas b/WebAdapter.pas index 6e32175..f4da801 100644 --- a/WebAdapter.pas +++ b/WebAdapter.pas @@ -563,7 +563,7 @@ begin FController.FWaterfallBuf, FController.FLastSMeter, FController.FVfoA, FController.FMode, FController.FFilterBW, FController.FAGCMode, FController.FAGCTop, - ViewSpan, FController.FVolume, + ViewSpan, FController.ActiveVolume, FController.FWfAGCEnabled, FController.FWfNFEnabled, FController.FCurrentBand, FController.FRunning and FController.FNetwork.Connected,