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:
+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
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user