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