feat(audio): раздельная громкость RX и TX-монитора + фикс синка слайдера

Две сохраняемые громкости: FVolume (RX) и FTxMonVolume (self-monitor
даунлинка на передаче, дефолт 40). Контекст монитора = TX + DUP + RX MUTE
off (тот же гейт, что аудио в движке). В этом контексте слайдер/SetVolume
правят FTxMonVolume, иначе FVolume; ActiveVolume — активная под контекст.
Автопереключение громкости RX<->TX-монитор на T/R (SetMOX) и при смене
RX MUTE / DUP во время передачи (ApplyActiveVolume). Обе персистятся
(tx_mon_volume в конфиге). Веб выровнен на ActiveVolume.

Фикс: слайдер громкости не синхронизировался с загруженным значением после
подключения (в rfDevice синкался только drive, volume пропущен) → слайдер
оставался на 70, движок брал сохранённое → «прыжок» при первом касании.
Добавлен TrkVolume.Position := ActiveVolume в rfDevice.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-07 16:24:57 +03:00
co-authored by Claude Opus 4.8
parent ff2082ab04
commit e13d610a23
4 changed files with 49 additions and 10 deletions
+3 -2
View File
@@ -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;
+41 -5
View File
@@ -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);
+4 -2
View File
@@ -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);
+1 -1
View File
@@ -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,