From c69d53785218532118324ffb6e23b4d231a01b7d Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 21 Jul 2026 23:01:59 +0300 Subject: [PATCH] =?UTF-8?q?feature(tx):=20=D1=81=D0=BB=D0=B0=D0=B9=D1=81?= =?UTF-8?q?=20=D0=B2=20FM=20RAW=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B7=D0=BD=D0=B0=D1=87=D0=B0=D1=82=D1=8C=20=D0=B8=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=87=D0=BD=D0=B8=D0=BA=D0=BE=D0=BC=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- RadioController.pas | 54 ++++++++++++++------------------------------- VfoOverlay.pas | 6 +++-- 2 files changed, 21 insertions(+), 39 deletions(-) diff --git a/RadioController.pas b/RadioController.pas index 9458732..996f726 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -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; // Слайсы могли ссылаться на общий вход (или держать свой поток на устройстве, diff --git a/VfoOverlay.pas b/VfoOverlay.pas index 2dfa2e7..0238b78 100644 --- a/VfoOverlay.pas +++ b/VfoOverlay.pas @@ -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 ---- }