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
+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);