mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(slices): аудио-устройства слайса на вкладке Slices (in/out на слот)
У каждого слайса B..G — свои Audio out / Audio in рядом с его CAT-портом.
Списки те же, что у главных комбо; пустой выбор (Default/None) = прежнее
поведение, общий выход/вход приложения.
Это устройство СЛОТА (буквы), а не шаблон:
• смена настройки применяется к слайсу, стоящему на слоте, немедленно
(SetSliceSlotAudio) — как и вся остальная форма настроек EWSDR;
• слайс, который встанет на слот позже (создание/restore из персиста),
получает то же устройство: слот сильнее аргумента AddSlice;
• выбор во вкладке AUD флага пишется обратно в настройку слота
(StoreSliceSlotAudio) — вкладка всегда показывает реальность, а не
вторую, конкурирующую истину.
Хранятся ИМЕНА PortAudio-устройств, а не индексы: порядок перечисления
между запусками плавает. Имя устройства, которого сейчас нет в системе,
не теряется — комбо добавляет его в список и держит выбранным.
ApplySliceSlotAudio зовётся на старте и на смене устройства (rfDevice):
слайсы восстанавливаются позже, слот должен знать свои звуковухи заранее.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+79
-3
@@ -341,6 +341,11 @@ type
|
||||
// назначает его TX-источником. Флаг живёт на слоте, а не на слайсе, потому
|
||||
// что настройка привязана к букве (B..G) и переживает удаление слайса.
|
||||
FSliceAutoTx: array[0..MAX_SLICES-1] of Boolean;
|
||||
// Аудио-устройства слота (буквы B..G): '' = общий выход приложения / общий
|
||||
// вход. Это НЕ шаблон, а привязка слота: применяется к живому слайсу сразу
|
||||
// и достаётся новому слайсу, который встанет на этот слот.
|
||||
FSliceOutDev: array[0..MAX_SLICES-1] of string;
|
||||
FSliceInDev: array[0..MAX_SLICES-1] of string;
|
||||
FVfoLock: Boolean; // блокировка перестройки VFO (Andromeda Lock)
|
||||
FDisplayDuplex: Boolean;
|
||||
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
|
||||
@@ -700,6 +705,11 @@ type
|
||||
function SliceSlotAutoTx(Slot: Integer): Boolean;
|
||||
procedure SetSliceSlotAutoTx(Slot: Integer; On_: Boolean);
|
||||
function SliceAutoTx(Id: Integer): Boolean;
|
||||
// Аудио-устройства слота. Сеттер применяет их к слайсу, который стоит на
|
||||
// слоте прямо сейчас (имя → PA-индекс текущей сессии); '' = общий.
|
||||
function SliceSlotOutDev(Slot: Integer): string;
|
||||
function SliceSlotInDev(Slot: Integer): string;
|
||||
procedure SetSliceSlotAudio(Slot: Integer; const OutDev, InDev: string);
|
||||
// Заявка на передачу ОТ ИМЕНИ слайса (CAT-порт слайса). Единственная точка,
|
||||
// где внешняя PTT может сменить TX-источник, поэтому здесь же — защита
|
||||
// «в эфире одновременно только один слайс». Выполнять в потоке контроллера.
|
||||
@@ -2018,6 +2028,8 @@ function TRadioController.AddSlice(TargetHz: Double;
|
||||
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
||||
var
|
||||
i, slot, Id: Integer;
|
||||
DevIndex_: Integer;
|
||||
DevName_: string;
|
||||
AO: TAudioOutput;
|
||||
begin
|
||||
Result := -1;
|
||||
@@ -2032,6 +2044,20 @@ begin
|
||||
if not FSlices[i].Active then begin slot := i; Break; end;
|
||||
if slot < 0 then Exit; // нет свободных слотов
|
||||
|
||||
// Устройство слота (настройка вкладки Slices) сильнее того, что попросил
|
||||
// вызывающий: слот — это буква флага, и звук у неё всегда один и тот же.
|
||||
if FSliceOutDev[slot] <> '' then
|
||||
begin
|
||||
DevName_ := FSliceOutDev[slot];
|
||||
DevIndex_ := -1;
|
||||
if Assigned(FAudioOut) then DevIndex_ := FAudioOut.FindDeviceByName(DevName_);
|
||||
end
|
||||
else
|
||||
begin
|
||||
DevName_ := DevName;
|
||||
DevIndex_ := DevIndex;
|
||||
end;
|
||||
|
||||
Id := FNextSliceId;
|
||||
if not FDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi,
|
||||
TargetHz - PanCenterHz(APanId), Vol, AGC,
|
||||
@@ -2041,7 +2067,7 @@ begin
|
||||
// Свой аудио-выход слайса на выбранной звуковухе.
|
||||
AO := TAudioOutput.Create(48000);
|
||||
AO.OutputBufferSize := FSettings.LoadAudioBufferSize;
|
||||
AO.DeviceIndex := DevIndex;
|
||||
AO.DeviceIndex := DevIndex_;
|
||||
if FLocalAudio then AO.Open;
|
||||
|
||||
FSlices[slot].Active := True;
|
||||
@@ -2064,8 +2090,8 @@ begin
|
||||
FSlices[slot].NBMode := 0;
|
||||
FSlices[slot].SNB := False;
|
||||
FSlices[slot].ANF := False;
|
||||
FSlices[slot].DeviceIndex := DevIndex;
|
||||
FSlices[slot].DevName := DevName;
|
||||
FSlices[slot].DeviceIndex := DevIndex_;
|
||||
FSlices[slot].DevName := DevName_;
|
||||
FSlices[slot].Audio := AO;
|
||||
// Вход слайса по умолчанию общий (FAudioIn); свой ставится SetSliceInDevice.
|
||||
FSlices[slot].InDeviceIndex := -1;
|
||||
@@ -2075,6 +2101,11 @@ begin
|
||||
if Mode = MODE_DMR then
|
||||
FSlices[slot].DMR := TDMRSliceDecoder.Create(Self, Id);
|
||||
|
||||
// Вход слота (настройка вкладки Slices) — тем же правилом, что и выход.
|
||||
if (FSliceInDev[slot] <> '') and Assigned(FAudioIn) then
|
||||
SetSliceInDeviceIdx(slot, FAudioIn.FindDeviceByName(FSliceInDev[slot]),
|
||||
FSliceInDev[slot]);
|
||||
|
||||
Result := Id;
|
||||
Changed(rfDevice);
|
||||
end;
|
||||
@@ -2326,6 +2357,7 @@ var idx: Integer;
|
||||
begin
|
||||
idx := FindSliceIndex(Id);
|
||||
if idx < 0 then Exit;
|
||||
FSliceOutDev[idx] := DevName; // выбор во флаге = устройство слота (одна истина)
|
||||
FSlices[idx].DeviceIndex := DevIndex;
|
||||
FSlices[idx].DevName := DevName;
|
||||
if FSlices[idx].Audio <> nil then
|
||||
@@ -2419,6 +2451,7 @@ procedure TRadioController.SetSliceInDeviceIdx(Idx, DevIndex: Integer;
|
||||
begin
|
||||
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
|
||||
if DevName = '' then DevIndex := -1;
|
||||
FSliceInDev[Idx] := DevName; // выбор во флаге = вход слота (одна истина)
|
||||
ReleaseMicInput(FSlices[Idx].AudioIn);
|
||||
FSlices[Idx].InDeviceIndex := DevIndex;
|
||||
FSlices[Idx].InDevName := DevName;
|
||||
@@ -2536,6 +2569,49 @@ begin
|
||||
Result := SliceSlotAutoTx(FindSliceIndex(Id));
|
||||
end;
|
||||
|
||||
function TRadioController.SliceSlotOutDev(Slot: Integer): string;
|
||||
begin
|
||||
if (Slot >= 0) and (Slot < MAX_SLICES) then Result := FSliceOutDev[Slot]
|
||||
else Result := '';
|
||||
end;
|
||||
|
||||
function TRadioController.SliceSlotInDev(Slot: Integer): string;
|
||||
begin
|
||||
if (Slot >= 0) and (Slot < MAX_SLICES) then Result := FSliceInDev[Slot]
|
||||
else Result := '';
|
||||
end;
|
||||
|
||||
procedure TRadioController.SetSliceSlotAudio(Slot: Integer; const OutDev, InDev: string);
|
||||
// Настройка = устройство слота, поэтому применяем сразу: слайс, который стоит
|
||||
// на слоте, переезжает на новое устройство (как выбор во вкладке AUD флага).
|
||||
var Id: Integer;
|
||||
begin
|
||||
if (Slot < 0) or (Slot >= MAX_SLICES) then Exit;
|
||||
Id := SliceIdBySlot(Slot);
|
||||
if FSliceOutDev[Slot] <> OutDev then
|
||||
begin
|
||||
FSliceOutDev[Slot] := OutDev;
|
||||
if Id > 0 then
|
||||
begin
|
||||
if (OutDev <> '') and Assigned(FAudioOut) then
|
||||
SetSliceDevice(Id, FAudioOut.FindDeviceByName(OutDev), OutDev)
|
||||
else
|
||||
SetSliceDevice(Id, -1, ''); // общий выход приложения
|
||||
end;
|
||||
end;
|
||||
if FSliceInDev[Slot] <> InDev then
|
||||
begin
|
||||
FSliceInDev[Slot] := InDev;
|
||||
if Id > 0 then
|
||||
begin
|
||||
if (InDev <> '') and Assigned(FAudioIn) then
|
||||
SetSliceInDevice(Id, FAudioIn.FindDeviceByName(InDev), InDev)
|
||||
else
|
||||
SetSliceInDevice(Id, -1, ''); // общий вход приложения
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TRadioController.RequestSliceTx(SliceId: Integer; On_: Boolean);
|
||||
// PTT, пришедшая по CAT-порту слайса. Аналог «Auto Switch TX Slice» в SmartSDR
|
||||
// CAT: с Auto TX слайс сам становится TX-источником, без него — передаёт,
|
||||
|
||||
Reference in New Issue
Block a user