mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
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:
+120
-14
@@ -94,6 +94,11 @@ type
|
||||
FDevIdx: array of Integer; // соответствующие PortAudio-индексы (-1=default)
|
||||
FCurDev: string; // имя текущего устройства ('' = default)
|
||||
|
||||
FInDevNames: TStringList; // имена устройств ВВОДА (микрофон/кабель)
|
||||
FInDevIdx: array of Integer; // их PortAudio-индексы
|
||||
FCurInDev: string; // текущий вход слайса ('' = общий вход)
|
||||
FAudInTab: Boolean; // AUD-панель показывает вход (IN), а не выход
|
||||
|
||||
FPanel: TFlyPanel; // открытая fly-out панель
|
||||
FAudScroll: Integer; // прокрутка списка аудио-устройств (верхний индекс)
|
||||
FVolTrack: TRect; // трек громкости (локальные координаты)
|
||||
@@ -108,6 +113,7 @@ type
|
||||
FOnVolume: TVolumeEvent;
|
||||
FOnMute: TNotifyEvent;
|
||||
FOnAudioDevice: TAudioDeviceEvent;
|
||||
FOnAudioInDevice: TAudioDeviceEvent;
|
||||
FOnSplit: TNotifyEvent;
|
||||
FOnTxSelect: TNotifyEvent;
|
||||
FOnRxMuteTx: TNotifyEvent;
|
||||
@@ -146,6 +152,11 @@ type
|
||||
procedure DrawModePanel(C: TCanvas; X, Y, RowW: Integer);
|
||||
procedure DrawDspPanel(C: TCanvas; X, Y, RowW: Integer);
|
||||
procedure DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
|
||||
// Строки активной вкладки AUD (OUT — устройства вывода, IN — ввода;
|
||||
// у IN первая строка псевдо: «общий вход» / «нет»).
|
||||
function AudRowCount: Integer;
|
||||
function AudRowName(I: Integer): string;
|
||||
function AudRowCurrent(I: Integer): Boolean;
|
||||
procedure RegisterHit(const HR: TRect; HV: Integer);
|
||||
procedure ApplyThemeColors; // раскладывает FTheme → структурные CLR_*
|
||||
function FormatFreq(Hz: Double): string;
|
||||
@@ -192,6 +203,9 @@ type
|
||||
procedure SetRxMuteTxState(AOn: Boolean); // бейдж RXM (self-monitor на TX)
|
||||
procedure SetAudioDevices(Names: TStrings; const Indices: array of Integer;
|
||||
const CurName: string);
|
||||
// Список устройств ВВОДА + текущий выбор ('' = общий вход приложения).
|
||||
procedure SetAudioInDevices(Names: TStrings; const Indices: array of Integer;
|
||||
const CurName: string);
|
||||
procedure UpdateSMeter(DB: Double);
|
||||
procedure UpdateVfo(AVfoHz: Double);
|
||||
procedure UpdateDMRStatus(const AText: string; Synced: Boolean);
|
||||
@@ -223,6 +237,7 @@ type
|
||||
property OnVolume: TVolumeEvent read FOnVolume write FOnVolume;
|
||||
property OnMute: TNotifyEvent read FOnMute write FOnMute;
|
||||
property OnAudioDevice: TAudioDeviceEvent read FOnAudioDevice write FOnAudioDevice;
|
||||
property OnAudioInDevice: TAudioDeviceEvent read FOnAudioInDevice write FOnAudioInDevice;
|
||||
property OnSplit: TNotifyEvent read FOnSplit write FOnSplit;
|
||||
property OnTxSelect: TNotifyEvent read FOnTxSelect write FOnTxSelect;
|
||||
property OnRxMuteTx: TNotifyEvent read FOnRxMuteTx write FOnRxMuteTx;
|
||||
@@ -262,6 +277,8 @@ const
|
||||
HIT_TX = -26;
|
||||
HIT_AUD_UP = -27;
|
||||
HIT_AUD_DN = -28;
|
||||
HIT_AUD_TAB_OUT = -34; // вкладка «OUT» в AUD-панели
|
||||
HIT_AUD_TAB_IN = -35; // вкладка «IN»
|
||||
HIT_SLICE_SEL = -29; // клик по бейджу буквы (выбрать слайс активным)
|
||||
HIT_CLOSE = -30; // × закрытия слайса (неглавные)
|
||||
HIT_SQL = -31; // тумблер FM-squelch (в DSP-панели)
|
||||
@@ -481,6 +498,9 @@ begin
|
||||
FVolDrag := False;
|
||||
FDevNames := TStringList.Create;
|
||||
FCurDev := '';
|
||||
FInDevNames := TStringList.Create;
|
||||
FCurInDev := '';
|
||||
FAudInTab := False; // AUD открывается на вкладке OUT
|
||||
FCacheBitmap := TBitmap.Create;
|
||||
FCacheBitmap.PixelFormat := pf32bit;
|
||||
FCacheDirty := True;
|
||||
@@ -520,6 +540,7 @@ end;
|
||||
|
||||
destructor TVfoOverlay.Destroy;
|
||||
begin
|
||||
FInDevNames.Free;
|
||||
FDevNames.Free;
|
||||
FCacheBitmap.Free;
|
||||
inherited Destroy;
|
||||
@@ -618,7 +639,9 @@ begin
|
||||
Result := ROW_H + IGAP + ROW_H; // toggles + AGC
|
||||
if IsFM then Inc(Result, IGAP + ROW_H); // + ряд SQL (только FM)
|
||||
end;
|
||||
fpAud: Result := Min(Max(1, FDevNames.Count), AUD_MAX_VIS) * AUD_ROW_H;
|
||||
// ряд вкладок OUT/IN + список устройств активной вкладки
|
||||
fpAud: Result := ROW_H + IGAP +
|
||||
Min(Max(1, AudRowCount), AUD_MAX_VIS) * AUD_ROW_H;
|
||||
else Result := 0;
|
||||
end;
|
||||
end;
|
||||
@@ -907,20 +930,63 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TVfoOverlay.AudRowCount: Integer;
|
||||
begin
|
||||
// У вкладки IN первая строка — «общий вход» (слайс) / «нет» (главный флаг).
|
||||
if FAudInTab then Result := FInDevNames.Count + 1
|
||||
else Result := FDevNames.Count;
|
||||
end;
|
||||
|
||||
function TVfoOverlay.AudRowName(I: Integer): string;
|
||||
begin
|
||||
if not FAudInTab then Exit(FDevNames[I]);
|
||||
if I = 0 then
|
||||
begin
|
||||
if FIsMain then Result := '(нет · мик радио)'
|
||||
else Result := '(общий вход)';
|
||||
Exit;
|
||||
end;
|
||||
Result := FInDevNames[I - 1];
|
||||
end;
|
||||
|
||||
function TVfoOverlay.AudRowCurrent(I: Integer): Boolean;
|
||||
begin
|
||||
if FAudInTab then
|
||||
begin
|
||||
if I = 0 then Exit(FCurInDev = '');
|
||||
Result := FInDevNames[I - 1] = FCurInDev;
|
||||
end
|
||||
else
|
||||
// Пустое имя вывода = устройство по умолчанию (первая строка списка).
|
||||
Result := (FDevNames[I] = FCurDev) or ((FCurDev = '') and (I = 0));
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.DrawAudPanel(C: TCanvas; X, Y, RowW: Integer);
|
||||
const
|
||||
ARROW_W = 15;
|
||||
var
|
||||
I, Slot, VisN, RowWidth, MaxScroll, PanelH, ArrowX, MidY, CY: Integer;
|
||||
HR: TRect;
|
||||
I, Slot, VisN, RowWidth, MaxScroll, PanelH, ArrowX, MidY, CY, Cnt: Integer;
|
||||
HR, TabR: TRect;
|
||||
Nm: string;
|
||||
Cur, Scrollable, CanUp, CanDn: Boolean;
|
||||
Tri: array[0..2] of TPoint;
|
||||
begin
|
||||
// Ряд вкладок: OUT (куда отдаём звук) / IN (откуда берём модуляцию).
|
||||
TabR := Rect(X, Y, X + (RowW - 4) div 2, Y + ROW_H);
|
||||
DrawButton(C, TabR, 'OUT', not FAudInTab,
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_AUD_TAB_OUT), 7);
|
||||
RegisterHit(TabR, HIT_AUD_TAB_OUT);
|
||||
TabR := Rect(TabR.Right + 4, Y, X + RowW, Y + ROW_H);
|
||||
DrawButton(C, TabR, 'IN', FAudInTab,
|
||||
(FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_AUD_TAB_IN), 7);
|
||||
RegisterHit(TabR, HIT_AUD_TAB_IN);
|
||||
Inc(Y, ROW_H + IGAP);
|
||||
|
||||
C.Font.Name := 'Courier New';
|
||||
C.Font.Size := 7;
|
||||
C.Font.Style := [];
|
||||
if FDevNames.Count = 0 then
|
||||
Cnt := AudRowCount;
|
||||
if Cnt = 0 then
|
||||
begin
|
||||
C.Font.Color := CLR_DIM;
|
||||
C.Brush.Style := bsClear;
|
||||
@@ -928,9 +994,9 @@ begin
|
||||
Exit;
|
||||
end;
|
||||
|
||||
VisN := Min(FDevNames.Count, AUD_MAX_VIS);
|
||||
Scrollable := FDevNames.Count > AUD_MAX_VIS;
|
||||
MaxScroll := FDevNames.Count - VisN;
|
||||
VisN := Min(Cnt, AUD_MAX_VIS);
|
||||
Scrollable := Cnt > AUD_MAX_VIS;
|
||||
MaxScroll := Cnt - VisN;
|
||||
if FAudScroll > MaxScroll then FAudScroll := MaxScroll;
|
||||
if FAudScroll < 0 then FAudScroll := 0;
|
||||
if Scrollable then RowWidth := RowW - ARROW_W - 2 else RowWidth := RowW;
|
||||
@@ -939,8 +1005,8 @@ begin
|
||||
begin
|
||||
I := FAudScroll + Slot;
|
||||
HR := Rect(X, Y + Slot*AUD_ROW_H, X + RowWidth, Y + Slot*AUD_ROW_H + 16);
|
||||
Nm := FDevNames[I];
|
||||
Cur := (Nm = FCurDev) or ((FCurDev = '') and (I = 0));
|
||||
Nm := AudRowName(I);
|
||||
Cur := AudRowCurrent(I);
|
||||
if Cur then C.Brush.Color := CLR_ACCENT_D
|
||||
else if (FHotIdx >= 0) and (FHitActions[FHotIdx] = HIT_DEV_BASE + I) then
|
||||
C.Brush.Color := CLR_BTN_HOT
|
||||
@@ -954,7 +1020,7 @@ begin
|
||||
if C.TextWidth(Nm) > RowWidth - 8 then
|
||||
while (Length(Nm) > 1) and (C.TextWidth(Nm + '…') > RowWidth - 8) do
|
||||
SetLength(Nm, Length(Nm) - 1);
|
||||
if Nm <> FDevNames[I] then Nm := Nm + '…';
|
||||
if Nm <> AudRowName(I) then Nm := Nm + '…';
|
||||
C.TextOut(HR.Left + 4, HR.Top + (16 - C.TextHeight('A')) div 2, Nm);
|
||||
RegisterHit(HR, HIT_DEV_BASE + I);
|
||||
end;
|
||||
@@ -1235,16 +1301,42 @@ begin
|
||||
HIT_CLOSE: begin if Assigned(FOnClose) then FOnClose(Self); Exit; end;
|
||||
HIT_AUD_UP: begin if FAudScroll > 0 then Dec(FAudScroll);
|
||||
RequestInvalidate; Exit; end;
|
||||
HIT_AUD_DN: begin if FAudScroll < FDevNames.Count - AUD_MAX_VIS then
|
||||
HIT_AUD_DN: begin if FAudScroll < AudRowCount - AUD_MAX_VIS then
|
||||
Inc(FAudScroll);
|
||||
RequestInvalidate; Exit; end;
|
||||
HIT_AUD_TAB_OUT,
|
||||
HIT_AUD_TAB_IN:
|
||||
begin
|
||||
if FAudInTab <> (HV = HIT_AUD_TAB_IN) then
|
||||
begin
|
||||
FAudInTab := HV = HIT_AUD_TAB_IN;
|
||||
FAudScroll := 0; // список другой длины
|
||||
Height := ComputeHeight;
|
||||
RequestInvalidate;
|
||||
end;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
// Устройство аудио
|
||||
// Устройство аудио (строка активной вкладки OUT/IN)
|
||||
if HV >= HIT_DEV_BASE then
|
||||
begin
|
||||
NewMode := HV - HIT_DEV_BASE; // индекс в списке
|
||||
if (NewMode >= 0) and (NewMode < FDevNames.Count) then
|
||||
NewMode := HV - HIT_DEV_BASE; // индекс строки
|
||||
if (NewMode < 0) or (NewMode >= AudRowCount) then Exit;
|
||||
if FAudInTab then
|
||||
begin
|
||||
// Строка 0 = «общий вход» (слайс) / «нет устройства» (главный флаг).
|
||||
if NewMode = 0 then FCurInDev := ''
|
||||
else FCurInDev := FInDevNames[NewMode - 1];
|
||||
FPanel := fpNone; Height := ComputeHeight;
|
||||
RequestInvalidate;
|
||||
if Assigned(FOnAudioInDevice) then
|
||||
begin
|
||||
if NewMode = 0 then FOnAudioInDevice(-1, '')
|
||||
else FOnAudioInDevice(FInDevIdx[NewMode - 1], FInDevNames[NewMode - 1]);
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
FCurDev := FDevNames[NewMode];
|
||||
FPanel := fpNone; Height := ComputeHeight;
|
||||
@@ -1543,6 +1635,20 @@ begin
|
||||
RequestInvalidate;
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.SetAudioInDevices(Names: TStrings;
|
||||
const Indices: array of Integer; const CurName: string);
|
||||
var
|
||||
I: Integer;
|
||||
begin
|
||||
FInDevNames.Assign(Names);
|
||||
SetLength(FInDevIdx, FInDevNames.Count);
|
||||
for I := 0 to FInDevNames.Count - 1 do
|
||||
if I <= High(Indices) then FInDevIdx[I] := Indices[I] else FInDevIdx[I] := -1;
|
||||
FCurInDev := CurName;
|
||||
if FPanel = fpAud then Height := ComputeHeight;
|
||||
RequestInvalidate;
|
||||
end;
|
||||
|
||||
procedure TVfoOverlay.UpdateSMeter(DB: Double);
|
||||
begin
|
||||
if Abs(DB - FSMeterDB) > FSMeterThreshold then
|
||||
|
||||
Reference in New Issue
Block a user