mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-26 04:47:35 +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:
+52
-3
@@ -581,7 +581,15 @@ type
|
|||||||
procedure OnSliceCATSettingsChange(
|
procedure OnSliceCATSettingsChange(
|
||||||
const Enabled: array of Boolean;
|
const Enabled: array of Boolean;
|
||||||
const Port: array of Integer;
|
const Port: array of Integer;
|
||||||
const AutoTX: array of Boolean);
|
const AutoTX: array of Boolean;
|
||||||
|
const OutDev: array of string;
|
||||||
|
const InDev: array of string);
|
||||||
|
// Выбор устройства во флаге слайса = устройство его слота: зеркалим в
|
||||||
|
// конфиг, чтобы вкладка Slices показывала то же самое.
|
||||||
|
procedure StoreSliceSlotAudio(SliceId: Integer);
|
||||||
|
// Аудио слотов из конфига → контроллер (старт и смена устройства): слайсы
|
||||||
|
// восстанавливаются позже и должны встать на свои звуковухи.
|
||||||
|
procedure ApplySliceSlotAudio;
|
||||||
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
procedure OnAlexSettingsChange(const A: TAlexSettings);
|
||||||
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
|
procedure OnXvtrSettingsChange(const X: TXvtrSettings);
|
||||||
procedure PushXvtrToWeb;
|
procedure PushXvtrToWeb;
|
||||||
@@ -1127,6 +1135,7 @@ begin
|
|||||||
FLightTheme := FController.FSettings.LoadTheme;
|
FLightTheme := FController.FSettings.LoadTheme;
|
||||||
FCATAdapter := TCATAdapter.Create(FController);
|
FCATAdapter := TCATAdapter.Create(FController);
|
||||||
FCATAdapter.ApplySettings(FCATLastGlobal);
|
FCATAdapter.ApplySettings(FCATLastGlobal);
|
||||||
|
ApplySliceSlotAudio;
|
||||||
FSMeterPeak := -130;
|
FSMeterPeak := -130;
|
||||||
FSMeterMin := -130;
|
FSMeterMin := -130;
|
||||||
FSMeterAvg := -130;
|
FSMeterAvg := -130;
|
||||||
@@ -1679,6 +1688,7 @@ begin
|
|||||||
// возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему.
|
// возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему.
|
||||||
FController.FLoadedGlobal.LightTheme := FLightTheme;
|
FController.FLoadedGlobal.LightTheme := FLightTheme;
|
||||||
FCATAdapter.ApplySettings(FController.FLoadedGlobal);
|
FCATAdapter.ApplySettings(FController.FLoadedGlobal);
|
||||||
|
ApplySliceSlotAudio;
|
||||||
RelabelBands; // HF ↔ VHF/UHF подписи под тип бэкенда
|
RelabelBands; // HF ↔ VHF/UHF подписи под тип бэкенда
|
||||||
ApplyBackendCapsToUI; // пресеты sample rate + гейтинг полей
|
ApplyBackendCapsToUI; // пресеты sample rate + гейтинг полей
|
||||||
RebuildXvtrButtons;
|
RebuildXvtrButtons;
|
||||||
@@ -5834,6 +5844,7 @@ begin
|
|||||||
if CurSliceId <> 0 then
|
if CurSliceId <> 0 then
|
||||||
begin
|
begin
|
||||||
FController.SetSliceDevice(CurSliceId, DevIndex, DevName);
|
FController.SetSliceDevice(CurSliceId, DevIndex, DevName);
|
||||||
|
StoreSliceSlotAudio(CurSliceId); // вкладка Slices показывает то же устройство
|
||||||
PushFlagStateAllPans(CurSliceId); // обновить подсветку текущего устройства слайса
|
PushFlagStateAllPans(CurSliceId); // обновить подсветку текущего устройства слайса
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -5848,6 +5859,7 @@ begin
|
|||||||
if CurSliceId <> 0 then
|
if CurSliceId <> 0 then
|
||||||
begin
|
begin
|
||||||
FController.SetSliceInDevice(CurSliceId, DevIndex, DevName);
|
FController.SetSliceInDevice(CurSliceId, DevIndex, DevName);
|
||||||
|
StoreSliceSlotAudio(CurSliceId);
|
||||||
PushFlagStateAllPans(CurSliceId);
|
PushFlagStateAllPans(CurSliceId);
|
||||||
Exit;
|
Exit;
|
||||||
end;
|
end;
|
||||||
@@ -8516,7 +8528,9 @@ begin
|
|||||||
SF.LoadSliceCATSettings(
|
SF.LoadSliceCATSettings(
|
||||||
FCATLastGlobal.CATSliceEnabled,
|
FCATLastGlobal.CATSliceEnabled,
|
||||||
FCATLastGlobal.CATSlicePort,
|
FCATLastGlobal.CATSlicePort,
|
||||||
FCATLastGlobal.CATSliceAutoTX);
|
FCATLastGlobal.CATSliceAutoTX,
|
||||||
|
FCATLastGlobal.CATSliceOutDev,
|
||||||
|
FCATLastGlobal.CATSliceInDev);
|
||||||
// Перечисляем PA устройства
|
// Перечисляем PA устройства
|
||||||
SF.RefreshAudioDevices(FController.FAudioOut, FController.FAudioIn);
|
SF.RefreshAudioDevices(FController.FAudioOut, FController.FAudioIn);
|
||||||
SF.LoadVisibility(FController.FShowSpectrum, FController.FShowWaterfall, FController.FShowWideband, FController.FWidebandFill,
|
SF.LoadVisibility(FController.FShowSpectrum, FController.FShowWaterfall, FController.FShowWideband, FController.FWidebandFill,
|
||||||
@@ -8622,7 +8636,9 @@ end;
|
|||||||
procedure TMainForm.OnSliceCATSettingsChange(
|
procedure TMainForm.OnSliceCATSettingsChange(
|
||||||
const Enabled: array of Boolean;
|
const Enabled: array of Boolean;
|
||||||
const Port: array of Integer;
|
const Port: array of Integer;
|
||||||
const AutoTX: array of Boolean);
|
const AutoTX: array of Boolean;
|
||||||
|
const OutDev: array of string;
|
||||||
|
const InDev: array of string);
|
||||||
// CAT дополнительных слайсов. Хранится там же, где остальные CAT-настройки
|
// CAT дополнительных слайсов. Хранится там же, где остальные CAT-настройки
|
||||||
// (корневая секция + device-blob), применяется тем же ApplySettings: адаптер
|
// (корневая секция + device-blob), применяется тем же ApplySettings: адаптер
|
||||||
// поднимет/опустит порты эндпоинтов и положит Auto TX в контроллер.
|
// поднимет/опустит порты эндпоинтов и положит Auto TX в контроллер.
|
||||||
@@ -8633,15 +8649,48 @@ begin
|
|||||||
if i <= High(Enabled) then FCATLastGlobal.CATSliceEnabled[i] := Enabled[i];
|
if i <= High(Enabled) then FCATLastGlobal.CATSliceEnabled[i] := Enabled[i];
|
||||||
if i <= High(Port) then FCATLastGlobal.CATSlicePort[i] := Port[i];
|
if i <= High(Port) then FCATLastGlobal.CATSlicePort[i] := Port[i];
|
||||||
if i <= High(AutoTX) then FCATLastGlobal.CATSliceAutoTX[i] := AutoTX[i];
|
if i <= High(AutoTX) then FCATLastGlobal.CATSliceAutoTX[i] := AutoTX[i];
|
||||||
|
if i <= High(OutDev) then FCATLastGlobal.CATSliceOutDev[i] := OutDev[i];
|
||||||
|
if i <= High(InDev) then FCATLastGlobal.CATSliceInDev[i] := InDev[i];
|
||||||
|
// Аудио слота — сразу в контроллер: слайс, стоящий на слоте, переезжает
|
||||||
|
// на новое устройство, следующий получит его при создании.
|
||||||
|
FController.SetSliceSlotAudio(i, FCATLastGlobal.CATSliceOutDev[i],
|
||||||
|
FCATLastGlobal.CATSliceInDev[i]);
|
||||||
end;
|
end;
|
||||||
FController.FLoadedGlobal.CATSliceEnabled := FCATLastGlobal.CATSliceEnabled;
|
FController.FLoadedGlobal.CATSliceEnabled := FCATLastGlobal.CATSliceEnabled;
|
||||||
FController.FLoadedGlobal.CATSlicePort := FCATLastGlobal.CATSlicePort;
|
FController.FLoadedGlobal.CATSlicePort := FCATLastGlobal.CATSlicePort;
|
||||||
FController.FLoadedGlobal.CATSliceAutoTX := FCATLastGlobal.CATSliceAutoTX;
|
FController.FLoadedGlobal.CATSliceAutoTX := FCATLastGlobal.CATSliceAutoTX;
|
||||||
|
FController.FLoadedGlobal.CATSliceOutDev := FCATLastGlobal.CATSliceOutDev;
|
||||||
|
FController.FLoadedGlobal.CATSliceInDev := FCATLastGlobal.CATSliceInDev;
|
||||||
FCATAdapter.ApplySettings(FCATLastGlobal);
|
FCATAdapter.ApplySettings(FCATLastGlobal);
|
||||||
FController.FSettings.SaveCATSettings(FCATLastGlobal);
|
FController.FSettings.SaveCATSettings(FCATLastGlobal);
|
||||||
FController.SaveGlobalSettings;
|
FController.SaveGlobalSettings;
|
||||||
FController.FSettings.Save;
|
FController.FSettings.Save;
|
||||||
RefreshTxIndicators; // бейджи AutoTX на флагах слайсов — сразу
|
RefreshTxIndicators; // бейджи AutoTX на флагах слайсов — сразу
|
||||||
|
for i := 0 to MAX_PANS - 1 do // подсветка текущего устройства во флагах
|
||||||
|
if FPans[i] <> nil then FPans[i].PushAllSliceFlagStates;
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.ApplySliceSlotAudio;
|
||||||
|
var i: Integer;
|
||||||
|
begin
|
||||||
|
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||||
|
FController.SetSliceSlotAudio(i, FCATLastGlobal.CATSliceOutDev[i],
|
||||||
|
FCATLastGlobal.CATSliceInDev[i]);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TMainForm.StoreSliceSlotAudio(SliceId: Integer);
|
||||||
|
// Слот слайса и его устройства уже обновлены контроллером (SetSliceDevice/
|
||||||
|
// SetSliceInDevice) — здесь только зеркалим их в конфиг и сохраняем.
|
||||||
|
var Slot: Integer;
|
||||||
|
begin
|
||||||
|
Slot := FController.SliceSlotOf(SliceId);
|
||||||
|
if (Slot < 0) or (Slot >= CFG_CAT_SLICE_COUNT) then Exit;
|
||||||
|
FCATLastGlobal.CATSliceOutDev[Slot] := FController.SliceSlotOutDev(Slot);
|
||||||
|
FCATLastGlobal.CATSliceInDev[Slot] := FController.SliceSlotInDev(Slot);
|
||||||
|
FController.FLoadedGlobal.CATSliceOutDev := FCATLastGlobal.CATSliceOutDev;
|
||||||
|
FController.FLoadedGlobal.CATSliceInDev := FCATLastGlobal.CATSliceInDev;
|
||||||
|
FController.FSettings.SaveCATSettings(FCATLastGlobal);
|
||||||
|
FController.FSettings.Save;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
end.
|
end.
|
||||||
|
|||||||
+79
-3
@@ -341,6 +341,11 @@ type
|
|||||||
// назначает его TX-источником. Флаг живёт на слоте, а не на слайсе, потому
|
// назначает его TX-источником. Флаг живёт на слоте, а не на слайсе, потому
|
||||||
// что настройка привязана к букве (B..G) и переживает удаление слайса.
|
// что настройка привязана к букве (B..G) и переживает удаление слайса.
|
||||||
FSliceAutoTx: array[0..MAX_SLICES-1] of Boolean;
|
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)
|
FVfoLock: Boolean; // блокировка перестройки VFO (Andromeda Lock)
|
||||||
FDisplayDuplex: Boolean;
|
FDisplayDuplex: Boolean;
|
||||||
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
|
// QO-100 full-duplex self-monitor: True — RX-аудио глушится на TX (по умолч.),
|
||||||
@@ -700,6 +705,11 @@ type
|
|||||||
function SliceSlotAutoTx(Slot: Integer): Boolean;
|
function SliceSlotAutoTx(Slot: Integer): Boolean;
|
||||||
procedure SetSliceSlotAutoTx(Slot: Integer; On_: Boolean);
|
procedure SetSliceSlotAutoTx(Slot: Integer; On_: Boolean);
|
||||||
function SliceAutoTx(Id: Integer): 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-порт слайса). Единственная точка,
|
// Заявка на передачу ОТ ИМЕНИ слайса (CAT-порт слайса). Единственная точка,
|
||||||
// где внешняя PTT может сменить TX-источник, поэтому здесь же — защита
|
// где внешняя PTT может сменить TX-источник, поэтому здесь же — защита
|
||||||
// «в эфире одновременно только один слайс». Выполнять в потоке контроллера.
|
// «в эфире одновременно только один слайс». Выполнять в потоке контроллера.
|
||||||
@@ -2018,6 +2028,8 @@ function TRadioController.AddSlice(TargetHz: Double;
|
|||||||
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
DevIndex: Integer; const DevName: string; APanId: Integer): Integer;
|
||||||
var
|
var
|
||||||
i, slot, Id: Integer;
|
i, slot, Id: Integer;
|
||||||
|
DevIndex_: Integer;
|
||||||
|
DevName_: string;
|
||||||
AO: TAudioOutput;
|
AO: TAudioOutput;
|
||||||
begin
|
begin
|
||||||
Result := -1;
|
Result := -1;
|
||||||
@@ -2032,6 +2044,20 @@ begin
|
|||||||
if not FSlices[i].Active then begin slot := i; Break; end;
|
if not FSlices[i].Active then begin slot := i; Break; end;
|
||||||
if slot < 0 then Exit; // нет свободных слотов
|
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;
|
Id := FNextSliceId;
|
||||||
if not FDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi,
|
if not FDSPEngine.AddSlice(Id, Mode, FilterLo, FilterHi,
|
||||||
TargetHz - PanCenterHz(APanId), Vol, AGC,
|
TargetHz - PanCenterHz(APanId), Vol, AGC,
|
||||||
@@ -2041,7 +2067,7 @@ begin
|
|||||||
// Свой аудио-выход слайса на выбранной звуковухе.
|
// Свой аудио-выход слайса на выбранной звуковухе.
|
||||||
AO := TAudioOutput.Create(48000);
|
AO := TAudioOutput.Create(48000);
|
||||||
AO.OutputBufferSize := FSettings.LoadAudioBufferSize;
|
AO.OutputBufferSize := FSettings.LoadAudioBufferSize;
|
||||||
AO.DeviceIndex := DevIndex;
|
AO.DeviceIndex := DevIndex_;
|
||||||
if FLocalAudio then AO.Open;
|
if FLocalAudio then AO.Open;
|
||||||
|
|
||||||
FSlices[slot].Active := True;
|
FSlices[slot].Active := True;
|
||||||
@@ -2064,8 +2090,8 @@ begin
|
|||||||
FSlices[slot].NBMode := 0;
|
FSlices[slot].NBMode := 0;
|
||||||
FSlices[slot].SNB := False;
|
FSlices[slot].SNB := False;
|
||||||
FSlices[slot].ANF := False;
|
FSlices[slot].ANF := False;
|
||||||
FSlices[slot].DeviceIndex := DevIndex;
|
FSlices[slot].DeviceIndex := DevIndex_;
|
||||||
FSlices[slot].DevName := DevName;
|
FSlices[slot].DevName := DevName_;
|
||||||
FSlices[slot].Audio := AO;
|
FSlices[slot].Audio := AO;
|
||||||
// Вход слайса по умолчанию общий (FAudioIn); свой ставится SetSliceInDevice.
|
// Вход слайса по умолчанию общий (FAudioIn); свой ставится SetSliceInDevice.
|
||||||
FSlices[slot].InDeviceIndex := -1;
|
FSlices[slot].InDeviceIndex := -1;
|
||||||
@@ -2075,6 +2101,11 @@ begin
|
|||||||
if Mode = MODE_DMR then
|
if Mode = MODE_DMR then
|
||||||
FSlices[slot].DMR := TDMRSliceDecoder.Create(Self, Id);
|
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;
|
Result := Id;
|
||||||
Changed(rfDevice);
|
Changed(rfDevice);
|
||||||
end;
|
end;
|
||||||
@@ -2326,6 +2357,7 @@ var idx: Integer;
|
|||||||
begin
|
begin
|
||||||
idx := FindSliceIndex(Id);
|
idx := FindSliceIndex(Id);
|
||||||
if idx < 0 then Exit;
|
if idx < 0 then Exit;
|
||||||
|
FSliceOutDev[idx] := DevName; // выбор во флаге = устройство слота (одна истина)
|
||||||
FSlices[idx].DeviceIndex := DevIndex;
|
FSlices[idx].DeviceIndex := DevIndex;
|
||||||
FSlices[idx].DevName := DevName;
|
FSlices[idx].DevName := DevName;
|
||||||
if FSlices[idx].Audio <> nil then
|
if FSlices[idx].Audio <> nil then
|
||||||
@@ -2419,6 +2451,7 @@ procedure TRadioController.SetSliceInDeviceIdx(Idx, DevIndex: Integer;
|
|||||||
begin
|
begin
|
||||||
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
|
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
|
||||||
if DevName = '' then DevIndex := -1;
|
if DevName = '' then DevIndex := -1;
|
||||||
|
FSliceInDev[Idx] := DevName; // выбор во флаге = вход слота (одна истина)
|
||||||
ReleaseMicInput(FSlices[Idx].AudioIn);
|
ReleaseMicInput(FSlices[Idx].AudioIn);
|
||||||
FSlices[Idx].InDeviceIndex := DevIndex;
|
FSlices[Idx].InDeviceIndex := DevIndex;
|
||||||
FSlices[Idx].InDevName := DevName;
|
FSlices[Idx].InDevName := DevName;
|
||||||
@@ -2536,6 +2569,49 @@ begin
|
|||||||
Result := SliceSlotAutoTx(FindSliceIndex(Id));
|
Result := SliceSlotAutoTx(FindSliceIndex(Id));
|
||||||
end;
|
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);
|
procedure TRadioController.RequestSliceTx(SliceId: Integer; On_: Boolean);
|
||||||
// PTT, пришедшая по CAT-порту слайса. Аналог «Auto Switch TX Slice» в SmartSDR
|
// PTT, пришедшая по CAT-порту слайса. Аналог «Auto Switch TX Slice» в SmartSDR
|
||||||
// CAT: с Auto TX слайс сам становится TX-источником, без него — передаёт,
|
// CAT: с Auto TX слайс сам становится TX-источником, без него — передаёт,
|
||||||
|
|||||||
@@ -460,6 +460,11 @@ type
|
|||||||
// Auto TX (аналог «Auto Switch TX Slice» в SmartSDR CAT): команда TX по
|
// Auto TX (аналог «Auto Switch TX Slice» в SmartSDR CAT): команда TX по
|
||||||
// CAT-порту слайса сама назначает этот слайс источником передачи.
|
// CAT-порту слайса сама назначает этот слайс источником передачи.
|
||||||
CATSliceAutoTX: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
CATSliceAutoTX: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
||||||
|
// Аудио слота слайса: имена PortAudio-устройств ('' = общий выход
|
||||||
|
// приложения / общий вход). Имена, а не индексы: порядок перечисления
|
||||||
|
// устройств меняется от запуска к запуску.
|
||||||
|
CATSliceOutDev: array[0..CFG_CAT_SLICE_COUNT-1] of string;
|
||||||
|
CATSliceInDev: array[0..CFG_CAT_SLICE_COUNT-1] of string;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
TSettingsManager = class
|
TSettingsManager = class
|
||||||
@@ -665,6 +670,8 @@ begin
|
|||||||
G.CATSliceEnabled[i] := False;
|
G.CATSliceEnabled[i] := False;
|
||||||
G.CATSlicePort[i] := CFG_CAT_SLICE_PORT0 + i;
|
G.CATSlicePort[i] := CFG_CAT_SLICE_PORT0 + i;
|
||||||
G.CATSliceAutoTX[i] := False;
|
G.CATSliceAutoTX[i] := False;
|
||||||
|
G.CATSliceOutDev[i] := '';
|
||||||
|
G.CATSliceInDev[i] := '';
|
||||||
end;
|
end;
|
||||||
// ADC — включены по умолчанию: поддерживаются на Hermes, Angelia, Orion
|
// ADC — включены по умолчанию: поддерживаются на Hermes, Angelia, Orion
|
||||||
G.DitherEnabled := True;
|
G.DitherEnabled := True;
|
||||||
@@ -923,6 +930,8 @@ begin
|
|||||||
G.CATSliceEnabled[i] := JB(GObj,'cat_slice_en_' +IntToStr(i), False);
|
G.CATSliceEnabled[i] := JB(GObj,'cat_slice_en_' +IntToStr(i), False);
|
||||||
G.CATSlicePort[i] := JI(GObj,'cat_slice_port_' +IntToStr(i), CFG_CAT_SLICE_PORT0 + i);
|
G.CATSlicePort[i] := JI(GObj,'cat_slice_port_' +IntToStr(i), CFG_CAT_SLICE_PORT0 + i);
|
||||||
G.CATSliceAutoTX[i] := JB(GObj,'cat_slice_autotx_'+IntToStr(i), False);
|
G.CATSliceAutoTX[i] := JB(GObj,'cat_slice_autotx_'+IntToStr(i), False);
|
||||||
|
G.CATSliceOutDev[i] := JS(GObj,'cat_slice_out_' +IntToStr(i), '');
|
||||||
|
G.CATSliceInDev[i] := JS(GObj,'cat_slice_in_' +IntToStr(i), '');
|
||||||
end;
|
end;
|
||||||
G.CATTcpEnabled := JB(GObj,'cat_tcp_enabled', False);
|
G.CATTcpEnabled := JB(GObj,'cat_tcp_enabled', False);
|
||||||
G.CATTcpPort := JI(GObj,'cat_tcp_port', 19090);
|
G.CATTcpPort := JI(GObj,'cat_tcp_port', 19090);
|
||||||
@@ -1062,6 +1071,8 @@ begin
|
|||||||
JW(O,'cat_slice_en_' +IntToStr(i), G.CATSliceEnabled[i]);
|
JW(O,'cat_slice_en_' +IntToStr(i), G.CATSliceEnabled[i]);
|
||||||
JW(O,'cat_slice_port_' +IntToStr(i), G.CATSlicePort[i]);
|
JW(O,'cat_slice_port_' +IntToStr(i), G.CATSlicePort[i]);
|
||||||
JW(O,'cat_slice_autotx_'+IntToStr(i), G.CATSliceAutoTX[i]);
|
JW(O,'cat_slice_autotx_'+IntToStr(i), G.CATSliceAutoTX[i]);
|
||||||
|
JWS(O,'cat_slice_out_' +IntToStr(i), G.CATSliceOutDev[i]);
|
||||||
|
JWS(O,'cat_slice_in_' +IntToStr(i), G.CATSliceInDev[i]);
|
||||||
end;
|
end;
|
||||||
JW(O,'cat_tcp_enabled',G.CATTcpEnabled);
|
JW(O,'cat_tcp_enabled',G.CATTcpEnabled);
|
||||||
JW(O,'cat_tcp_port',G.CATTcpPort);
|
JW(O,'cat_tcp_port',G.CATTcpPort);
|
||||||
@@ -1229,6 +1240,8 @@ begin
|
|||||||
JW(O, 'slice_en_' +IntToStr(i), G.CATSliceEnabled[i]);
|
JW(O, 'slice_en_' +IntToStr(i), G.CATSliceEnabled[i]);
|
||||||
JW(O, 'slice_port_' +IntToStr(i), G.CATSlicePort[i]);
|
JW(O, 'slice_port_' +IntToStr(i), G.CATSlicePort[i]);
|
||||||
JW(O, 'slice_autotx_'+IntToStr(i), G.CATSliceAutoTX[i]);
|
JW(O, 'slice_autotx_'+IntToStr(i), G.CATSliceAutoTX[i]);
|
||||||
|
JWS(O,'slice_out_' +IntToStr(i), G.CATSliceOutDev[i]);
|
||||||
|
JWS(O,'slice_in_' +IntToStr(i), G.CATSliceInDev[i]);
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@@ -1258,6 +1271,8 @@ begin
|
|||||||
G.CATSliceEnabled[i] := JB(O, 'slice_en_' +IntToStr(i), False);
|
G.CATSliceEnabled[i] := JB(O, 'slice_en_' +IntToStr(i), False);
|
||||||
G.CATSlicePort[i] := JI(O, 'slice_port_' +IntToStr(i), CFG_CAT_SLICE_PORT0 + i);
|
G.CATSlicePort[i] := JI(O, 'slice_port_' +IntToStr(i), CFG_CAT_SLICE_PORT0 + i);
|
||||||
G.CATSliceAutoTX[i] := JB(O, 'slice_autotx_'+IntToStr(i), False);
|
G.CATSliceAutoTX[i] := JB(O, 'slice_autotx_'+IntToStr(i), False);
|
||||||
|
G.CATSliceOutDev[i] := JS(O, 'slice_out_' +IntToStr(i), '');
|
||||||
|
G.CATSliceInDev[i] := JS(O, 'slice_in_' +IntToStr(i), '');
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
|||||||
+117
-6
@@ -80,7 +80,9 @@ type
|
|||||||
TOnSliceCATChange = procedure(
|
TOnSliceCATChange = procedure(
|
||||||
const Enabled: array of Boolean;
|
const Enabled: array of Boolean;
|
||||||
const Port: array of Integer;
|
const Port: array of Integer;
|
||||||
const AutoTX: array of Boolean) of object;
|
const AutoTX: array of Boolean;
|
||||||
|
const OutDev: array of string;
|
||||||
|
const InDev: array of string) of object;
|
||||||
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
TOnAlexChange = procedure(const A: TAlexSettings) of object;
|
||||||
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
||||||
|
|
||||||
@@ -346,6 +348,10 @@ type
|
|||||||
FSliceCATEn: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
FSliceCATEn: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
||||||
FSliceCATPort: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatSpinEdit;
|
FSliceCATPort: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatSpinEdit;
|
||||||
FSliceCATAutoTx: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
FSliceCATAutoTx: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
||||||
|
// Аудио слота: списки — общие с главными комбо (RefreshAudioDevices),
|
||||||
|
// первый пункт «Default»/«None» = общий выход/вход приложения.
|
||||||
|
FSliceOutDev: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatComboBox;
|
||||||
|
FSliceInDev: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatComboBox;
|
||||||
|
|
||||||
// ---- Advanced tab controls ----
|
// ---- Advanced tab controls ----
|
||||||
FChkDither: TFlatCheckBox;
|
FChkDither: TFlatCheckBox;
|
||||||
@@ -473,6 +479,8 @@ type
|
|||||||
procedure OnCATTcpChange(Sender: TObject);
|
procedure OnCATTcpChange(Sender: TObject);
|
||||||
procedure FireCATChange;
|
procedure FireCATChange;
|
||||||
procedure OnSliceCATCtrlChange(Sender: TObject);
|
procedure OnSliceCATCtrlChange(Sender: TObject);
|
||||||
|
procedure SelectDevInCombo(Cmb: TFlatComboBox; const DevName: string);
|
||||||
|
procedure FillSliceDevCombos(AudioOut: TAudioOutput; AudioIn: TAudioInput);
|
||||||
procedure FireSliceCATChange;
|
procedure FireSliceCATChange;
|
||||||
procedure BtnCloseClick(Sender: TObject);
|
procedure BtnCloseClick(Sender: TObject);
|
||||||
|
|
||||||
@@ -527,7 +535,9 @@ type
|
|||||||
procedure LoadSliceCATSettings(
|
procedure LoadSliceCATSettings(
|
||||||
const Enabled: array of Boolean;
|
const Enabled: array of Boolean;
|
||||||
const Port: array of Integer;
|
const Port: array of Integer;
|
||||||
const AutoTX: array of Boolean);
|
const AutoTX: array of Boolean;
|
||||||
|
const OutDev: array of string;
|
||||||
|
const InDev: array of string);
|
||||||
procedure LoadCATSettings(
|
procedure LoadCATSettings(
|
||||||
const SerEnabled: array of Boolean;
|
const SerEnabled: array of Boolean;
|
||||||
const SerPort: array of string;
|
const SerPort: array of string;
|
||||||
@@ -592,6 +602,9 @@ const
|
|||||||
BTN_H = 24;
|
BTN_H = 24;
|
||||||
GRP_TITLE_H = 22; // высота заголовка в group-панелях (title label + divider)
|
GRP_TITLE_H = 22; // высота заголовка в group-панелях (title label + divider)
|
||||||
|
|
||||||
|
// Первый пункт комбо аудио слайса = «как у приложения» (пустое имя в конфиге).
|
||||||
|
SLICE_DEV_DEFAULT = 'Default (app output)';
|
||||||
|
SLICE_DEV_NONE = 'None (app input)';
|
||||||
CAT_BAUD_VALS: array[0..7] of Integer = (1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200);
|
CAT_BAUD_VALS: array[0..7] of Integer = (1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200);
|
||||||
CAT_BAUD_NAMES: array[0..7] of string = ('1200','2400','4800','9600','19200','38400','57600','115200');
|
CAT_BAUD_NAMES: array[0..7] of string = ('1200','2400','4800','9600','19200','38400','57600','115200');
|
||||||
|
|
||||||
@@ -2844,6 +2857,54 @@ begin
|
|||||||
FLoading := False;
|
FLoading := False;
|
||||||
Names.Free;
|
Names.Free;
|
||||||
end;
|
end;
|
||||||
|
FillSliceDevCombos(AudioOut, AudioIn);
|
||||||
|
end;
|
||||||
|
|
||||||
|
procedure TSettingsForm.FillSliceDevCombos(AudioOut: TAudioOutput; AudioIn: TAudioInput);
|
||||||
|
// Списки устройств в комбо вкладки Slices — те же, что у главных комбо. Текущий
|
||||||
|
// выбор держим по ИМЕНИ: порядок перечисления PortAudio между запусками плавает.
|
||||||
|
var
|
||||||
|
Names: TStringList;
|
||||||
|
Idx: array[0..63] of Integer;
|
||||||
|
Cnt, i, k: Integer;
|
||||||
|
Cur: string;
|
||||||
|
begin
|
||||||
|
if FSliceOutDev[0] = nil then Exit; // вкладка ещё не построена
|
||||||
|
Names := TStringList.Create;
|
||||||
|
FLoading := True;
|
||||||
|
try
|
||||||
|
if AudioOut <> nil then
|
||||||
|
begin
|
||||||
|
Cnt := 0; Names.Clear;
|
||||||
|
AudioOut.EnumOutputDevices(Names, Idx, Cnt);
|
||||||
|
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||||
|
begin
|
||||||
|
Cur := FSliceOutDev[i].Text;
|
||||||
|
if FSliceOutDev[i].ItemIndex = 0 then Cur := '';
|
||||||
|
FSliceOutDev[i].Items.Clear;
|
||||||
|
FSliceOutDev[i].Items.Add(SLICE_DEV_DEFAULT);
|
||||||
|
for k := 0 to Cnt - 1 do FSliceOutDev[i].Items.Add(Names[k]);
|
||||||
|
SelectDevInCombo(FSliceOutDev[i], Cur);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
if AudioIn <> nil then
|
||||||
|
begin
|
||||||
|
Cnt := 0; Names.Clear;
|
||||||
|
AudioIn.EnumInputDevices(Names, Idx, Cnt);
|
||||||
|
for i := 0 to CFG_CAT_SLICE_COUNT - 1 do
|
||||||
|
begin
|
||||||
|
Cur := FSliceInDev[i].Text;
|
||||||
|
if FSliceInDev[i].ItemIndex = 0 then Cur := '';
|
||||||
|
FSliceInDev[i].Items.Clear;
|
||||||
|
FSliceInDev[i].Items.Add(SLICE_DEV_NONE);
|
||||||
|
for k := 0 to Cnt - 1 do FSliceInDev[i].Items.Add(Names[k]);
|
||||||
|
SelectDevInCombo(FSliceInDev[i], Cur);
|
||||||
|
end;
|
||||||
|
end;
|
||||||
|
finally
|
||||||
|
FLoading := False;
|
||||||
|
Names.Free;
|
||||||
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -3560,9 +3621,10 @@ const
|
|||||||
PAD = 18;
|
PAD = 18;
|
||||||
LW = 120;
|
LW = 120;
|
||||||
CX = PAD + LW + 8;
|
CX = PAD + LW + 8;
|
||||||
GRP_W = 320;
|
GRP_W = 380;
|
||||||
GRP_H = 168;
|
GRP_H = 240;
|
||||||
GRP_GAP = 16;
|
GRP_GAP = 16;
|
||||||
|
CMB_W = 210;
|
||||||
R1 = 42;
|
R1 = 42;
|
||||||
STEP = 32;
|
STEP = 32;
|
||||||
var
|
var
|
||||||
@@ -3570,6 +3632,7 @@ var
|
|||||||
Grp: TPanel;
|
Grp: TPanel;
|
||||||
Chk: TFlatCheckBox;
|
Chk: TFlatCheckBox;
|
||||||
Spin: TFlatSpinEdit;
|
Spin: TFlatSpinEdit;
|
||||||
|
Cmb: TFlatComboBox;
|
||||||
begin
|
begin
|
||||||
with TLabel.Create(Self) do
|
with TLabel.Create(Self) do
|
||||||
begin
|
begin
|
||||||
@@ -3643,10 +3706,35 @@ begin
|
|||||||
|
|
||||||
MakeLbl(Grp, 'CAT TX takes transmit to this slice',
|
MakeLbl(Grp, 'CAT TX takes transmit to this slice',
|
||||||
PAD + 2, R1 + 2 * STEP + 24, GRP_W - PAD * 2 - 2);
|
PAD + 2, R1 + 2 * STEP + 24, GRP_W - PAD * 2 - 2);
|
||||||
|
|
||||||
|
// Аудио слота: не шаблон, а привязка буквы — применяется к слайсу, который
|
||||||
|
// стоит на слоте сейчас, и достаётся следующему.
|
||||||
|
MakeLbl(Grp, 'Audio out:', PAD, R1 + 3 * STEP + 26, LW);
|
||||||
|
Cmb := MakeCombo(Grp, CX, R1 + 3 * STEP + 20, CMB_W, OnSliceCATCtrlChange);
|
||||||
|
Cmb.Tag := i;
|
||||||
|
Cmb.Items.Add(SLICE_DEV_DEFAULT);
|
||||||
|
Cmb.ItemIndex := 0;
|
||||||
|
FSliceOutDev[i] := Cmb;
|
||||||
|
|
||||||
|
MakeLbl(Grp, 'Audio in:', PAD, R1 + 4 * STEP + 26, LW);
|
||||||
|
Cmb := MakeCombo(Grp, CX, R1 + 4 * STEP + 20, CMB_W, OnSliceCATCtrlChange);
|
||||||
|
Cmb.Tag := i;
|
||||||
|
Cmb.Items.Add(SLICE_DEV_NONE);
|
||||||
|
Cmb.ItemIndex := 0;
|
||||||
|
FSliceInDev[i] := Cmb;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
gy := 96 + ((CFG_CAT_SLICE_COUNT + 1) div 2) * (GRP_H + GRP_GAP);
|
gy := 96 + ((CFG_CAT_SLICE_COUNT + 1) div 2) * (GRP_H + GRP_GAP);
|
||||||
with TLabel.Create(Self) do
|
with TLabel.Create(Self) do
|
||||||
|
begin
|
||||||
|
Parent := FPageSlices;
|
||||||
|
Caption := 'Audio out/in belong to the slice letter: the setting moves the '
|
||||||
|
+ 'slice sitting on that slot right away and the next one inherits it.';
|
||||||
|
SetBounds(MARGIN, gy, 760, 20);
|
||||||
|
Font.Name := UI_FONT; Font.Size := 9; Font.Color := CLR_TEXTDIM;
|
||||||
|
end;
|
||||||
|
Inc(gy, 18);
|
||||||
|
with TLabel.Create(Self) do
|
||||||
begin
|
begin
|
||||||
Parent := FPageSlices;
|
Parent := FPageSlices;
|
||||||
Caption := 'Only one slice transmits at a time: while anything is already on '
|
Caption := 'Only one slice transmits at a time: while anything is already on '
|
||||||
@@ -3676,6 +3764,7 @@ procedure TSettingsForm.FireSliceCATChange;
|
|||||||
var
|
var
|
||||||
En, Auto: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
En, Auto: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
||||||
Prt: array[0..CFG_CAT_SLICE_COUNT-1] of Integer;
|
Prt: array[0..CFG_CAT_SLICE_COUNT-1] of Integer;
|
||||||
|
OutD, InD: array[0..CFG_CAT_SLICE_COUNT-1] of string;
|
||||||
i: Integer;
|
i: Integer;
|
||||||
begin
|
begin
|
||||||
if FLoading then Exit;
|
if FLoading then Exit;
|
||||||
@@ -3685,8 +3774,13 @@ begin
|
|||||||
En[i] := FSliceCATEn[i].Checked;
|
En[i] := FSliceCATEn[i].Checked;
|
||||||
Prt[i] := FSliceCATPort[i].Value;
|
Prt[i] := FSliceCATPort[i].Value;
|
||||||
Auto[i] := FSliceCATAutoTx[i].Checked;
|
Auto[i] := FSliceCATAutoTx[i].Checked;
|
||||||
|
// Первый пункт списка — «общий выход/вход приложения» (пустое имя).
|
||||||
|
if FSliceOutDev[i].ItemIndex <= 0 then OutD[i] := ''
|
||||||
|
else OutD[i] := FSliceOutDev[i].Text;
|
||||||
|
if FSliceInDev[i].ItemIndex <= 0 then InD[i] := ''
|
||||||
|
else InD[i] := FSliceInDev[i].Text;
|
||||||
end;
|
end;
|
||||||
FOnSliceCATChange(En, Prt, Auto);
|
FOnSliceCATChange(En, Prt, Auto, OutD, InD);
|
||||||
end;
|
end;
|
||||||
|
|
||||||
procedure TSettingsForm.OnSliceCATCtrlChange(Sender: TObject);
|
procedure TSettingsForm.OnSliceCATCtrlChange(Sender: TObject);
|
||||||
@@ -3695,7 +3789,9 @@ begin FireSliceCATChange; end;
|
|||||||
procedure TSettingsForm.LoadSliceCATSettings(
|
procedure TSettingsForm.LoadSliceCATSettings(
|
||||||
const Enabled: array of Boolean;
|
const Enabled: array of Boolean;
|
||||||
const Port: array of Integer;
|
const Port: array of Integer;
|
||||||
const AutoTX: array of Boolean);
|
const AutoTX: array of Boolean;
|
||||||
|
const OutDev: array of string;
|
||||||
|
const InDev: array of string);
|
||||||
var i: Integer;
|
var i: Integer;
|
||||||
begin
|
begin
|
||||||
FLoading := True;
|
FLoading := True;
|
||||||
@@ -3706,12 +3802,27 @@ begin
|
|||||||
if (i <= High(Port)) and (Port[i] > 0) then
|
if (i <= High(Port)) and (Port[i] > 0) then
|
||||||
FSliceCATPort[i].Value := EnsureRange(Port[i], 1, 65535);
|
FSliceCATPort[i].Value := EnsureRange(Port[i], 1, 65535);
|
||||||
if i <= High(AutoTX) then FSliceCATAutoTx[i].Checked := AutoTX[i];
|
if i <= High(AutoTX) then FSliceCATAutoTx[i].Checked := AutoTX[i];
|
||||||
|
if i <= High(OutDev) then SelectDevInCombo(FSliceOutDev[i], OutDev[i]);
|
||||||
|
if i <= High(InDev) then SelectDevInCombo(FSliceInDev[i], InDev[i]);
|
||||||
end;
|
end;
|
||||||
finally
|
finally
|
||||||
FLoading := False;
|
FLoading := False;
|
||||||
end;
|
end;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
procedure TSettingsForm.SelectDevInCombo(Cmb: TFlatComboBox; const DevName: string);
|
||||||
|
// Выбор устройства по ИМЕНИ. Сохранённого устройства может не быть в системе
|
||||||
|
// (кабель не поднят, гарнитура не воткнута) — тогда добавляем имя в список,
|
||||||
|
// чтобы привязка слота не потерялась молча при первом же открытии настроек.
|
||||||
|
var i: Integer;
|
||||||
|
begin
|
||||||
|
if Cmb = nil then Exit;
|
||||||
|
if DevName = '' then begin Cmb.ItemIndex := 0; Exit; end;
|
||||||
|
i := Cmb.Items.IndexOf(DevName);
|
||||||
|
if i < 0 then i := Cmb.Items.Add(DevName);
|
||||||
|
Cmb.ItemIndex := i;
|
||||||
|
end;
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// BuildAlexTab
|
// BuildAlexTab
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
+21
-1
@@ -134,7 +134,27 @@ SmartSDR: Transmit Slice переключён, а не одолжен). Бейд
|
|||||||
## 4. Настройки
|
## 4. Настройки
|
||||||
|
|
||||||
Вкладка **Slices**: на каждый слайс B..G — `Enable CAT TCP server`, `Port`
|
Вкладка **Slices**: на каждый слайс B..G — `Enable CAT TCP server`, `Port`
|
||||||
(по умолчанию 19091, 19092, …) и `Auto TX`.
|
(по умолчанию 19091, 19092, …), `Auto TX`, `Audio out`, `Audio in`.
|
||||||
|
|
||||||
|
### 4.1 Аудио слота
|
||||||
|
|
||||||
|
`Audio out`/`Audio in` — это **устройство слота (буквы)**, а не шаблон:
|
||||||
|
|
||||||
|
* смена настройки применяется немедленно к слайсу, который стоит на слоте
|
||||||
|
сейчас (`TRadioController.SetSliceSlotAudio`);
|
||||||
|
* слайс, который встанет на слот позже (создание/восстановление из персиста),
|
||||||
|
получает то же устройство — оно сильнее того, что попросил вызывающий
|
||||||
|
`AddSlice`;
|
||||||
|
* выбор во вкладке `AUD` флага слайса пишется обратно в настройку слота
|
||||||
|
(`SetSliceDevice`/`SetSliceInDevice` → `MainForm.StoreSliceSlotAudio`), так
|
||||||
|
что вкладка всегда показывает реальное устройство — одна истина, два места
|
||||||
|
правки;
|
||||||
|
* пусто (`Default (app output)` / `None (app input)`) — как раньше: общий выход
|
||||||
|
приложения и общий вход.
|
||||||
|
|
||||||
|
Хранятся **имена** PortAudio-устройств, а не индексы: порядок перечисления между
|
||||||
|
запусками плавает. Имя отсутствующего сейчас устройства (кабель не поднят) не
|
||||||
|
теряется — комбо добавляет его в список и держит выбранным.
|
||||||
|
|
||||||
* Хранятся вместе с остальным CAT: корневая секция `cat` (`slice_en_N`,
|
* Хранятся вместе с остальным CAT: корневая секция `cat` (`slice_en_N`,
|
||||||
`slice_port_N`, `slice_autotx_N`) + device-blob (`cat_slice_*`), не привязаны
|
`slice_port_N`, `slice_autotx_N`) + device-blob (`cat_slice_*`), не привязаны
|
||||||
|
|||||||
Reference in New Issue
Block a user