feat(tx): TX-профиль на слайс — пилюля в шапке флага

Передатчик один, профиль до сих пор был один на устройство: активный профиль
звучал независимо от того, какой слайс взял передачу. С панадаптером на FT8 и
вторым на FM это значит, что компрессия и EQ голосового профиля уходили в
эфир на FT8 (FMRAW прикрыт сам — ApplyTXModeSettings глушит там всю обработку,
а DIGU/DIGL нет). У Flex это решается дисциплиной и внешним API; делаем в радио.

Привязка «источник передачи → профиль» (-1 = не переключать, звучит активный):
TCtrlSlice.TXProfile для B..G, TTXProfileList.MainBind для главного (слайс A).
Персист: ключ tx_profile в слайсе пана и main_bind в секции tx_profiles.
SetTxSlice зовёт ApplyBoundTXProfile ДО пуша режима и цепи, иначе
ApplyTXSettingsToDSP лёг бы на старый режим. Привязали источник, который
передаёт прямо сейчас, — применяем сразу.

Привязки — индексы, поэтому RemapTXProfileBindings: удаление профиля из
середины снимает привязки на него и сдвигает те, что правее; Factory-сброс
снимает все (список заменён целиком).

UI: пилюля с именем профиля в первой строке флага сразу за шириной фильтра
(ЛКМ — fly-out список, первая строка «As active»). Правая граница считается
до SPLIT у главного и до RXM у слайса; не рисуем при пустом списке, в DMR
(передачи нет) и в FMRAW (профиль там ни на что не влияет). Прокрутка списка
общая с AUD — блок стрелок вынесен в DrawScrollArrows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-04 17:06:08 +03:00
co-authored by Claude Opus 5
parent 8563c4e4cc
commit 013d0406ac
5 changed files with 397 additions and 41 deletions
+84
View File
@@ -178,6 +178,9 @@ type
InDeviceIndex: Integer; // PortAudio-индекс ввода (-1 = общий)
InDevName: string;
AudioIn: TAudioInput; // НЕ владеет: ссылка на поток из mic-пула
// TX-профиль слайса: индекс в FTXProfiles или -1 = «не переключать».
// Применяется, когда слайс становится TX-источником (SetTxSlice).
TXProfile: Integer;
DMR: TDMRSliceDecoder; // владеет; nil для не-DMR слайса
end;
@@ -596,6 +599,11 @@ type
procedure RenameTXProfile(Idx: Integer; const AName: string);
procedure DeleteTXProfile(Idx: Integer);
procedure ResetTXProfilesToFactory; // заводской набор заново
// Профиль на TX-источник: -1 = не переключать (звучит активный).
function SliceTXProfile(SliceId: Integer): Integer;
procedure SetSliceTXProfile(SliceId, ProfIdx: Integer);
procedure RemapTXProfileBindings(Deleted: Integer); // после удаления/замены списка
procedure ApplyBoundTXProfile; // профиль текущего TX-источника → в эфир
procedure SaveTXProfilesToConfig;
// ---- Управление диапазонами (состояние + DSP; рендер через события) ----
@@ -2148,6 +2156,7 @@ begin
FSlices[slot].RxMuteOnTx := True; // себя на передаче по умолчанию не слышим
FSlices[slot].FMSQOn := False; // squelch по умолчанию выключен
FSlices[slot].FMSQLevel := 40;
FSlices[slot].TXProfile := -1; // звучит тот профиль, что активен
FSlices[slot].NRMode := 0; // DSP слайса стартует выключенным
FSlices[slot].NBMode := 0;
FSlices[slot].SNB := False;
@@ -2940,6 +2949,9 @@ begin
else if FTransmitting then SetMOX(False);
end;
FTxSliceId := SliceId;
// Профиль, привязанный к новому источнику, — ДО пуша режима и цепи: иначе
// ApplyTXSettingsToDSP внутри SelectTXProfile лёг бы на старый режим.
ApplyBoundTXProfile;
// Передающий тракт под режим нового источника (idle-канал, применится к PTT).
if FWDSPReady and Assigned(FDSPEngine) then
begin
@@ -5033,6 +5045,9 @@ begin
for i := Idx to FTXProfiles.Count - 2 do
FTXProfiles.Items[i] := FTXProfiles.Items[i + 1];
Dec(FTXProfiles.Count);
// Привязки флагов — это ИНДЕКСЫ: после сдвига списка их надо переписать,
// иначе слайс молча начнёт включать соседний профиль.
RemapTXProfileBindings(Idx);
if FTXProfiles.ActiveIdx > Idx then Dec(FTXProfiles.ActiveIdx)
else if FTXProfiles.ActiveIdx = Idx then
begin
@@ -5047,6 +5062,74 @@ begin
Changed(rfTXProfile);
end;
procedure TRadioController.RemapTXProfileBindings(Deleted: Integer);
// Профиль удалён из середины списка: привязку на него снимаем, привязки на
// профили правее — сдвигаем. Deleted < 0 = список заменён целиком, снимаем все.
procedure Fix(var B: Integer);
begin
if B < 0 then Exit;
if Deleted < 0 then B := -1
else if B = Deleted then B := -1
else if B > Deleted then Dec(B);
end;
var i: Integer;
begin
Fix(FTXProfiles.MainBind);
for i := 0 to High(FSlices) do
if FSlices[i].Active then Fix(FSlices[i].TXProfile);
end;
function TRadioController.SliceTXProfile(SliceId: Integer): Integer;
// Привязка «этот источник передачи → этот профиль». -1 = не переключать.
var idx: Integer;
begin
Result := -1;
if SliceId <= 0 then
Result := FTXProfiles.MainBind
else
begin
idx := FindSliceIndex(SliceId);
if idx >= 0 then Result := FSlices[idx].TXProfile;
end;
if (Result < 0) or (Result >= FTXProfiles.Count) then Result := -1;
end;
procedure TRadioController.SetSliceTXProfile(SliceId, ProfIdx: Integer);
var idx: Integer;
begin
if (ProfIdx < -1) or (ProfIdx >= FTXProfiles.Count) then ProfIdx := -1;
if SliceId <= 0 then
begin
if FTXProfiles.MainBind = ProfIdx then Exit;
FTXProfiles.MainBind := ProfIdx;
SaveTXProfilesToConfig;
end
else
begin
idx := FindSliceIndex(SliceId);
if (idx < 0) or (FSlices[idx].TXProfile = ProfIdx) then Exit;
FSlices[idx].TXProfile := ProfIdx;
// Персист слайсов делает MainForm на общем сохранении панов — здесь только
// состояние. Привязку главного пишем сразу: она живёт в секции профилей.
end;
// Привязали источник, который передаёт прямо сейчас, — применяем немедленно,
// иначе профиль подхватился бы только при следующей смене TX-источника.
if SliceId = FTxSliceId then ApplyBoundTXProfile;
Changed(rfTXProfile);
end;
procedure TRadioController.ApplyBoundTXProfile;
// Профиль текущего TX-источника → в эфир. Ничего не делает, если источник ни к
// чему не привязан (-1) или нужный профиль уже активен.
var P: Integer;
begin
P := SliceTXProfile(FTxSliceId);
if (P < 0) or (P = FTXProfiles.ActiveIdx) then Exit;
SelectTXProfile(P);
end;
procedure TRadioController.ResetTXProfilesToFactory;
// Пересобирает заводской набор поверх текущего списка. База — ЖИВЫЕ настройки,
// поэтому «Default» = микрофонный вход, полоса, динамика и мощность как сейчас,
@@ -5055,6 +5138,7 @@ procedure TRadioController.ResetTXProfilesToFactory;
// пересоздаётся.
begin
DefaultTXProfiles(FTXSettings, FDrivePercent, FTXProfiles);
RemapTXProfileBindings(-1); // список другой — старые привязки бессмысленны
// ActiveIdx гасим перед Select: иначе StoreActiveTXProfile внутри него
// впишет в свежий «Default» живой EQ и сброс тембра тут же отменится.
FTXProfiles.ActiveIdx := -1;