mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +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(
|
||||
const Enabled: array of Boolean;
|
||||
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 OnXvtrSettingsChange(const X: TXvtrSettings);
|
||||
procedure PushXvtrToWeb;
|
||||
@@ -1127,6 +1135,7 @@ begin
|
||||
FLightTheme := FController.FSettings.LoadTheme;
|
||||
FCATAdapter := TCATAdapter.Create(FController);
|
||||
FCATAdapter.ApplySettings(FCATLastGlobal);
|
||||
ApplySliceSlotAudio;
|
||||
FSMeterPeak := -130;
|
||||
FSMeterMin := -130;
|
||||
FSMeterAvg := -130;
|
||||
@@ -1679,6 +1688,7 @@ begin
|
||||
// возвращаем живое значение в persist-буфер, чтобы blob нёс актуальную тему.
|
||||
FController.FLoadedGlobal.LightTheme := FLightTheme;
|
||||
FCATAdapter.ApplySettings(FController.FLoadedGlobal);
|
||||
ApplySliceSlotAudio;
|
||||
RelabelBands; // HF ↔ VHF/UHF подписи под тип бэкенда
|
||||
ApplyBackendCapsToUI; // пресеты sample rate + гейтинг полей
|
||||
RebuildXvtrButtons;
|
||||
@@ -5834,6 +5844,7 @@ begin
|
||||
if CurSliceId <> 0 then
|
||||
begin
|
||||
FController.SetSliceDevice(CurSliceId, DevIndex, DevName);
|
||||
StoreSliceSlotAudio(CurSliceId); // вкладка Slices показывает то же устройство
|
||||
PushFlagStateAllPans(CurSliceId); // обновить подсветку текущего устройства слайса
|
||||
Exit;
|
||||
end;
|
||||
@@ -5848,6 +5859,7 @@ begin
|
||||
if CurSliceId <> 0 then
|
||||
begin
|
||||
FController.SetSliceInDevice(CurSliceId, DevIndex, DevName);
|
||||
StoreSliceSlotAudio(CurSliceId);
|
||||
PushFlagStateAllPans(CurSliceId);
|
||||
Exit;
|
||||
end;
|
||||
@@ -8516,7 +8528,9 @@ begin
|
||||
SF.LoadSliceCATSettings(
|
||||
FCATLastGlobal.CATSliceEnabled,
|
||||
FCATLastGlobal.CATSlicePort,
|
||||
FCATLastGlobal.CATSliceAutoTX);
|
||||
FCATLastGlobal.CATSliceAutoTX,
|
||||
FCATLastGlobal.CATSliceOutDev,
|
||||
FCATLastGlobal.CATSliceInDev);
|
||||
// Перечисляем PA устройства
|
||||
SF.RefreshAudioDevices(FController.FAudioOut, FController.FAudioIn);
|
||||
SF.LoadVisibility(FController.FShowSpectrum, FController.FShowWaterfall, FController.FShowWideband, FController.FWidebandFill,
|
||||
@@ -8622,7 +8636,9 @@ end;
|
||||
procedure TMainForm.OnSliceCATSettingsChange(
|
||||
const Enabled: array of Boolean;
|
||||
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-настройки
|
||||
// (корневая секция + device-blob), применяется тем же ApplySettings: адаптер
|
||||
// поднимет/опустит порты эндпоинтов и положит Auto TX в контроллер.
|
||||
@@ -8633,15 +8649,48 @@ begin
|
||||
if i <= High(Enabled) then FCATLastGlobal.CATSliceEnabled[i] := Enabled[i];
|
||||
if i <= High(Port) then FCATLastGlobal.CATSlicePort[i] := Port[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;
|
||||
FController.FLoadedGlobal.CATSliceEnabled := FCATLastGlobal.CATSliceEnabled;
|
||||
FController.FLoadedGlobal.CATSlicePort := FCATLastGlobal.CATSlicePort;
|
||||
FController.FLoadedGlobal.CATSliceAutoTX := FCATLastGlobal.CATSliceAutoTX;
|
||||
FController.FLoadedGlobal.CATSliceOutDev := FCATLastGlobal.CATSliceOutDev;
|
||||
FController.FLoadedGlobal.CATSliceInDev := FCATLastGlobal.CATSliceInDev;
|
||||
FCATAdapter.ApplySettings(FCATLastGlobal);
|
||||
FController.FSettings.SaveCATSettings(FCATLastGlobal);
|
||||
FController.SaveGlobalSettings;
|
||||
FController.FSettings.Save;
|
||||
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.
|
||||
|
||||
+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-источником, без него — передаёт,
|
||||
|
||||
@@ -460,6 +460,11 @@ type
|
||||
// Auto TX (аналог «Auto Switch TX Slice» в SmartSDR CAT): команда TX по
|
||||
// CAT-порту слайса сама назначает этот слайс источником передачи.
|
||||
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;
|
||||
|
||||
TSettingsManager = class
|
||||
@@ -665,6 +670,8 @@ begin
|
||||
G.CATSliceEnabled[i] := False;
|
||||
G.CATSlicePort[i] := CFG_CAT_SLICE_PORT0 + i;
|
||||
G.CATSliceAutoTX[i] := False;
|
||||
G.CATSliceOutDev[i] := '';
|
||||
G.CATSliceInDev[i] := '';
|
||||
end;
|
||||
// ADC — включены по умолчанию: поддерживаются на Hermes, Angelia, Orion
|
||||
G.DitherEnabled := True;
|
||||
@@ -923,6 +930,8 @@ begin
|
||||
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.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;
|
||||
G.CATTcpEnabled := JB(GObj,'cat_tcp_enabled', False);
|
||||
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_port_' +IntToStr(i), G.CATSlicePort[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;
|
||||
JW(O,'cat_tcp_enabled',G.CATTcpEnabled);
|
||||
JW(O,'cat_tcp_port',G.CATTcpPort);
|
||||
@@ -1229,6 +1240,8 @@ begin
|
||||
JW(O, 'slice_en_' +IntToStr(i), G.CATSliceEnabled[i]);
|
||||
JW(O, 'slice_port_' +IntToStr(i), G.CATSlicePort[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;
|
||||
|
||||
@@ -1258,6 +1271,8 @@ begin
|
||||
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.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;
|
||||
|
||||
|
||||
+117
-6
@@ -80,7 +80,9 @@ type
|
||||
TOnSliceCATChange = procedure(
|
||||
const Enabled: array of Boolean;
|
||||
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;
|
||||
TOnXvtrChange = procedure(const X: TXvtrSettings) of object;
|
||||
|
||||
@@ -346,6 +348,10 @@ type
|
||||
FSliceCATEn: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatCheckBox;
|
||||
FSliceCATPort: array[0..CFG_CAT_SLICE_COUNT-1] of TFlatSpinEdit;
|
||||
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 ----
|
||||
FChkDither: TFlatCheckBox;
|
||||
@@ -473,6 +479,8 @@ type
|
||||
procedure OnCATTcpChange(Sender: TObject);
|
||||
procedure FireCATChange;
|
||||
procedure OnSliceCATCtrlChange(Sender: TObject);
|
||||
procedure SelectDevInCombo(Cmb: TFlatComboBox; const DevName: string);
|
||||
procedure FillSliceDevCombos(AudioOut: TAudioOutput; AudioIn: TAudioInput);
|
||||
procedure FireSliceCATChange;
|
||||
procedure BtnCloseClick(Sender: TObject);
|
||||
|
||||
@@ -527,7 +535,9 @@ type
|
||||
procedure LoadSliceCATSettings(
|
||||
const Enabled: array of Boolean;
|
||||
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(
|
||||
const SerEnabled: array of Boolean;
|
||||
const SerPort: array of string;
|
||||
@@ -592,6 +602,9 @@ const
|
||||
BTN_H = 24;
|
||||
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_NAMES: array[0..7] of string = ('1200','2400','4800','9600','19200','38400','57600','115200');
|
||||
|
||||
@@ -2844,6 +2857,54 @@ begin
|
||||
FLoading := False;
|
||||
Names.Free;
|
||||
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;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -3560,9 +3621,10 @@ const
|
||||
PAD = 18;
|
||||
LW = 120;
|
||||
CX = PAD + LW + 8;
|
||||
GRP_W = 320;
|
||||
GRP_H = 168;
|
||||
GRP_W = 380;
|
||||
GRP_H = 240;
|
||||
GRP_GAP = 16;
|
||||
CMB_W = 210;
|
||||
R1 = 42;
|
||||
STEP = 32;
|
||||
var
|
||||
@@ -3570,6 +3632,7 @@ var
|
||||
Grp: TPanel;
|
||||
Chk: TFlatCheckBox;
|
||||
Spin: TFlatSpinEdit;
|
||||
Cmb: TFlatComboBox;
|
||||
begin
|
||||
with TLabel.Create(Self) do
|
||||
begin
|
||||
@@ -3643,10 +3706,35 @@ begin
|
||||
|
||||
MakeLbl(Grp, 'CAT TX takes transmit to this slice',
|
||||
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;
|
||||
|
||||
gy := 96 + ((CFG_CAT_SLICE_COUNT + 1) div 2) * (GRP_H + GRP_GAP);
|
||||
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
|
||||
Parent := FPageSlices;
|
||||
Caption := 'Only one slice transmits at a time: while anything is already on '
|
||||
@@ -3676,6 +3764,7 @@ procedure TSettingsForm.FireSliceCATChange;
|
||||
var
|
||||
En, Auto: array[0..CFG_CAT_SLICE_COUNT-1] of Boolean;
|
||||
Prt: array[0..CFG_CAT_SLICE_COUNT-1] of Integer;
|
||||
OutD, InD: array[0..CFG_CAT_SLICE_COUNT-1] of string;
|
||||
i: Integer;
|
||||
begin
|
||||
if FLoading then Exit;
|
||||
@@ -3685,8 +3774,13 @@ begin
|
||||
En[i] := FSliceCATEn[i].Checked;
|
||||
Prt[i] := FSliceCATPort[i].Value;
|
||||
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;
|
||||
FOnSliceCATChange(En, Prt, Auto);
|
||||
FOnSliceCATChange(En, Prt, Auto, OutD, InD);
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.OnSliceCATCtrlChange(Sender: TObject);
|
||||
@@ -3695,7 +3789,9 @@ begin FireSliceCATChange; end;
|
||||
procedure TSettingsForm.LoadSliceCATSettings(
|
||||
const Enabled: array of Boolean;
|
||||
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;
|
||||
begin
|
||||
FLoading := True;
|
||||
@@ -3706,12 +3802,27 @@ begin
|
||||
if (i <= High(Port)) and (Port[i] > 0) then
|
||||
FSliceCATPort[i].Value := EnsureRange(Port[i], 1, 65535);
|
||||
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;
|
||||
finally
|
||||
FLoading := False;
|
||||
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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
+21
-1
@@ -134,7 +134,27 @@ SmartSDR: Transmit Slice переключён, а не одолжен). Бейд
|
||||
## 4. Настройки
|
||||
|
||||
Вкладка **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`,
|
||||
`slice_port_N`, `slice_autotx_N`) + device-blob (`cat_slice_*`), не привязаны
|
||||
|
||||
Reference in New Issue
Block a user