mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
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:
+3
-2
@@ -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
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user