feature(tx): слайс в FM RAW можно назначать источником передачи

TX-бейдж на флаге слайса был заблокирован и для DMR, и для FM RAW, хотя тракт
FM RAW передавать умеет: SetTxSlice отбрасывает только DMR, а SetTXMode уводит
FM RAW в WFM с плоской девиацией. RX-only остаётся один DMR (нет AMBE-кодера).

- VfoOverlay.TxSelectionAllowed: запрет сузился до DMR.
- RadioController.SetSliceMode: если режим меняют у слайса, который сейчас
  источник TX, передающий тракт перестраивается под новый режим. Без этого
  выбор FM RAW на уже выбранном TX-слайсе оставлял в эфире прежнюю модуляцию
  до следующего клика по бейджу. На переходах в/из FM RAW заодно пересматривается
  источник модуляции (внешний PCM) со сбросом кольца входа — как в главном
  SetMode.

Заодно снята временная диагностика [MIC] (MicDbg и её вызовы) из mic-пула:
зависание при выборе на слайсе входа из настроек больше не воспроизводится.

Проверено на железе: TX со слайса в FM RAW работает. Подробное тестирование —
следующим шагом.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-21 23:01:59 +03:00
co-authored by Claude Opus 4.8
parent 93ab03f1eb
commit c69d537852
2 changed files with 21 additions and 39 deletions
+17 -37
View File
@@ -2019,6 +2019,21 @@ begin
if (Mode = MODE_DMR) and (FTxSliceId = Id) then SetTxSlice(0);
if (OldMode = MODE_DMR) and (Mode <> MODE_DMR) then
FreeAndNil(FSlices[idx].DMR);
// Этот слайс — источник передачи: передающий тракт идёт за его режимом
// (иначе после смены режима в эфир ушёл бы прежний вид модуляции). Вход FM
// RAW — внешний PCM, поэтому заодно пересматриваем источник модуляции.
if (FTxSliceId = Id) and (Mode <> OldMode) and
FWDSPReady and Assigned(FDSPEngine) then
begin
FDSPEngine.SetTXMode(ActiveTXMode);
if (Mode = MODE_FMRAW) or (OldMode = MODE_FMRAW) then
begin
if Mode = MODE_FMRAW then FWebMicActive := False;
FDSPEngine.SetTXMicSource(DefaultMicSource);
if Assigned(ActiveMicInput) and ActiveMicInput.IsOpen then
ActiveMicInput.Flush;
end;
end;
end;
procedure TRadioController.SetSliceFilter(Id, Low, High: Integer);
@@ -2197,14 +2212,6 @@ end;
// --- Пул входных потоков -----------------------------------------------------
procedure MicDbg(const S: string);
// ВРЕМЕННО: диагностика mic-пула в stderr (поиск причины зависания/отказа
// Pa_OpenStream при выборе входа на слайсе). Снять после разбора.
begin
WriteLn(StdErr, '[MIC] ', S);
Flush(StdErr);
end;
function TRadioController.AcquireMicInput(DevIndex: Integer): TAudioInput;
// Ссылка на открытый поток устройства DevIndex (создаёт при первом обращении).
// DevIndex<0 = «своего входа нет» → nil (читатель упадёт на общий FAudioIn).
@@ -2213,25 +2220,14 @@ var
AI: TAudioInput;
begin
Result := nil;
if Assigned(FAudioIn) then
MicDbg(Format('Acquire(dev=%d): main FAudioIn dev=%d open=%s name="%s"',
[DevIndex, FAudioIn.DeviceIndex, BoolToStr(FAudioIn.IsOpen, True),
FAudioInDevName]))
else
MicDbg(Format('Acquire(dev=%d): FAudioIn=nil', [DevIndex]));
if DevIndex < 0 then Exit;
// Общий вход уже открыт на этом же устройстве — переиспользуем его: второй
// Open того же PA-устройства на эксклюзивном ALSA-hw просто не прошёл бы.
if Assigned(FAudioIn) and (FAudioIn.DeviceIndex = DevIndex) then
begin
MicDbg(' -> общий FAudioIn (без открытия)');
Exit(FAudioIn);
end;
for i := 0 to High(FMicPool) do
if FMicPool[i].DevIdx = DevIndex then
begin
MicDbg(Format(' -> пул[%d] refs=%d open=%s',
[i, FMicPool[i].Refs, BoolToStr(FMicPool[i].AI.IsOpen, True)]));
// Запись с Refs=0 — поток закрыт, но объект жив (см. ReleaseMicInput).
if (FMicPool[i].Refs <= 0) and FLocalAudio and
(not FMicPool[i].AI.IsOpen) then
@@ -2242,15 +2238,7 @@ begin
AI := TAudioInput.Create(48000);
AI.DeviceIndex := DevIndex;
if FLocalAudio then
begin
MicDbg(Format(' новый поток: Open(dev=%d) ...', [DevIndex]));
try
MicDbg(Format(' Open(dev=%d) = %s err="%s"',
[DevIndex, BoolToStr(AI.Open, True), AI.LastError]));
except
on E: Exception do MicDbg(' Open исключение: ' + E.Message);
end;
end;
try AI.Open; except end;
n := Length(FMicPool);
SetLength(FMicPool, n + 1);
FMicPool[n].DevIdx := DevIndex;
@@ -2304,8 +2292,6 @@ procedure TRadioController.SetSliceInDeviceIdx(Idx, DevIndex: Integer;
const DevName: string);
begin
if (Idx < 0) or (Idx >= MAX_SLICES) or (not FSlices[Idx].Active) then Exit;
MicDbg(Format('SetSliceInDevice: slot=%d id=%d dev=%d name="%s"',
[Idx, FSlices[Idx].Id, DevIndex, DevName]));
if DevName = '' then DevIndex := -1;
ReleaseMicInput(FSlices[Idx].AudioIn);
FSlices[Idx].InDeviceIndex := DevIndex;
@@ -2321,18 +2307,12 @@ procedure TRadioController.SetMainMicDevice(DevIndex: Integer;
const DevName: string);
var i: Integer;
begin
MicDbg(Format('SetMainMicDevice: dev=%d name="%s"', [DevIndex, DevName]));
if Assigned(FAudioIn) then
begin
if FAudioIn.IsOpen then FAudioIn.Close;
FAudioIn.DeviceIndex := DevIndex;
if (DevIndex >= 0) and FLocalAudio then
try
MicDbg(Format(' main Open(dev=%d) = %s err="%s"',
[DevIndex, BoolToStr(FAudioIn.Open, True), FAudioIn.LastError]));
except
on E: Exception do MicDbg(' main Open исключение: ' + E.Message);
end;
try FAudioIn.Open; except end;
end;
FAudioInDevName := DevName;
// Слайсы могли ссылаться на общий вход (или держать свой поток на устройстве,
+4 -2
View File
@@ -463,8 +463,10 @@ end;
function TVfoOverlay.TxSelectionAllowed: Boolean;
begin
// Main A remains the normal TX source. Additional DMR slices are RX-only.
Result := FIsMain or not (FMode in [OVL_MODE_DMR, OVL_MODE_FMRAW]);
// Main A remains the normal TX source. DMR slices stay RX-only (no AMBE
// encoder). FM RAW transmits external PCM from its own input, so it is a
// valid TX source.
Result := FIsMain or (FMode <> OVL_MODE_DMR);
end;
{ ---- Lifecycle ---- }