wip(audio): выбор устройства ВВОДА на каждом слайсе + вкладки OUT/IN в AUD

Паритет с уже существующим выбором вывода: на каждом слайсе своё устройство
входа (микрофон/виртуальный кабель).

- RadioController: TCtrlSlice += InDeviceIndex/InDevName/AudioIn (ссылка, не
  владение); mic-пул FMicPool с refcount (Acquire/ReleaseMicInput) — один
  открытый TAudioInput на уникальное PA-устройство, смена TX-слайса не
  переоткрывает поток; ActiveMicInput/ActiveMicInDevName для PullSoundCardMic,
  Flush и DefaultMicSource; SetMainMicDevice перепривязывает ссылки слайсов.
- VfoOverlay: во fly-out AUD ряд вкладок OUT | IN, событие OnAudioInDevice.
- PanafallPanel: проброс списка входных устройств во флаги слайсов и главный.
- Settings: персист in_dev_name.

НЕ ГОТОВО: зависание GUI при выборе на слайсе того же входа, что в настройках
(пул открывает поток вместо шортката на общий FAudioIn). В коде оставлена
временная диагностика [MIC] в stderr. Также подозрение на замедление потока
данных от радио — проверяем сравнением с main.

Со мной в коммите временные тестовые программы PortAudio (other/pa*.lpr).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 21:52:24 +03:00
co-authored by Claude Opus 4.8
parent 317e9b732c
commit 4aa180e7b2
9 changed files with 493 additions and 49 deletions
+28 -13
View File
@@ -599,6 +599,7 @@ type
procedure OnVfoOverlayVolume(Vol: Integer);
procedure OnVfoOverlayMute(Sender: TObject);
procedure OnVfoOverlayAudioDevice(DevIndex: Integer; const DevName: string);
procedure OnVfoOverlayAudioInDevice(DevIndex: Integer; const DevName: string);
procedure OnVfoOverlaySplit(Sender: TObject);
procedure OnVfoOverlayTxSelect(Sender: TObject);
procedure OnVfoOverlayRxMuteTx(Sender: TObject); // RXM — приём слайса на TX
@@ -3838,8 +3839,10 @@ function TMainForm.DefaultMicSource: TTXMicSource;
// иначе используем HW mic-стрим от трансивера.
// HW PTT всегда переключает на txmsRadio непосредственно в ApplyMOX.
begin
if FController.FAudioInDevName <> '' then Result := txmsSoundCard
else Result := txmsRadio;
// Учитываем вход TX-слайса: у него может быть свой (виртуальный кабель),
// даже когда общего входа нет — иначе взяли бы микрофон радио.
if FController.ActiveMicInDevName <> '' then Result := txmsSoundCard
else Result := txmsRadio;
end;
// ApplyTXSettingsToDSP переехал в TRadioController (вся TX-цепь над FTXSettings).
@@ -5739,6 +5742,20 @@ begin
FPan.PushMainFlagAudioDevices; // обновить подсветку текущего устройства
end;
procedure TMainForm.OnVfoOverlayAudioInDevice(DevIndex: Integer; const DevName: string);
// Вкладка IN во флаге: откуда слайс берёт модуляцию на передаче. У флага
// главного VFO это общий вход приложения (то же поле, что в настройках).
begin
if CurSliceId <> 0 then
begin
FController.SetSliceInDevice(CurSliceId, DevIndex, DevName);
PushFlagStateAllPans(CurSliceId);
Exit;
end;
ApplyAudioInputDevice(DevIndex, DevName);
FPan.PushMainFlagAudioDevices;
end;
procedure TMainForm.OnVfoOverlayInvalidate(Sender: TObject);
var P: TPanafallPanel;
begin
@@ -5797,6 +5814,7 @@ begin
O.OnVolume := OnVfoOverlayVolume;
O.OnMute := OnVfoOverlayMute;
O.OnAudioDevice := OnVfoOverlayAudioDevice;
O.OnAudioInDevice := OnVfoOverlayAudioInDevice;
O.OnSplit := OnVfoOverlaySplit;
O.OnTxSelect := OnVfoOverlayTxSelect;
O.OnRxMuteTx := OnVfoOverlayRxMuteTx;
@@ -6742,6 +6760,7 @@ begin
Cfg.Pans[i].Slices[n].FMSQOn := FController.FSlices[s].FMSQOn;
Cfg.Pans[i].Slices[n].FMSQLevel := FController.FSlices[s].FMSQLevel;
Cfg.Pans[i].Slices[n].DevName := FController.FSlices[s].DevName;
Cfg.Pans[i].Slices[n].InDevName := FController.FSlices[s].InDevName;
Inc(n);
end;
end;
@@ -6762,6 +6781,10 @@ begin
TWDSPAGCMode(EnsureRange(SC.AGC, 0, Ord(High(TWDSPAGCMode)))),
EnsureRange(SC.Volume, 0.0, 1.0), DevIdx, SC.DevName, PanId);
if Id < 0 then Exit;
// Свой вход слайса (имя устройства → PA-индекс текущей сессии).
if SC.InDevName <> '' then
FController.SetSliceInDevice(Id,
FController.FAudioIn.FindDeviceByName(SC.InDevName), SC.InDevName);
if SC.Muted then FController.SetSliceMute(Id, True);
FController.SetSliceRxMuteOnTx(Id, SC.RxMuteOnTx);
if SC.FMSQOn then FController.SetSliceFMSquelch(Id, True, SC.FMSQLevel);
@@ -8176,18 +8199,10 @@ begin
end;
procedure TMainForm.ApplyAudioInputDevice(DevIndex: Integer; const DevName: string);
// Общий вход: контроллер сам переоткрывает поток и перепривязывает к нему
// слайсы, которые ссылались на общее устройство (mic-пул).
begin
if FController.FAudioIn.IsOpen then
FController.FAudioIn.Close;
FController.FAudioIn.DeviceIndex := DevIndex;
FController.FAudioInDevName := DevName;
if DevIndex >= 0 then
begin
try
FController.FAudioIn.Open;
except
end;
end;
FController.SetMainMicDevice(DevIndex, DevName);
end;
procedure TMainForm.ApplyAudioBufferSize(BufferSize: Integer);