From 0a1a1ee3afd6bc236e3506d9a82b31962cb61649 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 10 Aug 2026 13:14:38 +0300 Subject: [PATCH] =?UTF-8?q?fix(cw):=20=D0=BF=D0=B5=D1=80=D0=B2=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B3=D0=BE=D0=BD=20=D0=BD=D0=B0=20=D0=B6?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=B7=D0=B5=20=E2=80=94=20=D1=87=D0=B0=D1=81?= =?UTF-8?q?=D1=82=D0=BE=D1=82=D0=B0,=20=D0=BC=D0=BE=D1=89=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C,=20=D0=B8=D0=BD=D0=B4=D0=B8=D0=BA=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F,=20=D0=BF=D0=BE=D0=BB=D0=BE=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Семь дефектов, вскрытых проверкой в эфире. Общий корень у большинства: с кейером в прошивке FTransmitting не поднимается вовсе — передачей владеет FPGA, — а половина проекта считала этот флаг признаком эфира. Обрыв посылки после первого элемента. При break-in железо само поднимает T/R на каждую посылку, HPS_PTT это отражает, и разбор статуса гонял по фронту SetMOX(True/False); снятие MOX гасило CWX. Теперь в телеграфе фронт HPS_PTT не управляет передачей. Без этого и работа манипулятором пересобирала бы передающий тракт на каждой точке. Обрыв по «key hit» переведён на фронт лепестка: на плате без ключа входы могут стоять в единице, и проверка уровня убивала бы передачу сразу. Посылка уходила по центру дисплея, а не на VFO. StartRunning ставил DUC на центр DDC; до первой перестройки VFO или до первого MOX там и оставалось. Без CTUN центр совпадает с VFO, поэтому баг и дожил до телеграфа, где MOX не бывает. SetRunAndFreq получает TX-частоту, SetSliceTarget толкает кадр, когда перестраивают слайс-источник. Правило: DUC обязан стоять правильно всегда, а не только на передаче. Мощность ниже, чем на TUN: уровень (байт 345) клался в кадр только при FIsTransmitting. Введён SetCWKeyerArmed — пока кейер вооружён, уровень лежит в каждом HP-кадре. Там же пересчитывается FDriveLevel, иначе до первого касания ручки в железо уходил ноль. Индикация не показывала передачу: события rfTransmitting в телеграфе не бывает, поэтому блок рендера не выполнялся ни разу. ApplyHPStatus шлёт его сам на фронтах RadioKeyed («железо в эфире» = передача или замыкание ключа прошивкой, выдержка 8 точек и не меньше 500 мс). На RadioKeyed переведены S-метр, красная полоса главного VFO и TX-бейджи флагов вместе с красной полосой слайса на панадаптерах. Полоса на спектре рисовалась как SSB: TXSignedEdges обязана отдавать для CW голосовую боковую (через bp0 идёт тон pitch при настройке), поэтому экранные кромки развязаны в TXFilterEdgesHz — занимаемая полоса манипуляции симметрично несущей. Тот же класс, что был у ЧМ. F1..F8 молчали, пока не откроешь окно памяти: обработчик упирался в проверку лениво создаваемой формы. Текст берётся из настроек. Co-Authored-By: Claude Opus 5 --- HPSDRNetwork.pas | 16 +++++++- MainForm.pas | 25 ++++++++---- PanafallPanel.pas | 4 +- RadioBackend.pas | 9 +++++ RadioController.pas | 98 ++++++++++++++++++++++++++++++++++++++++++--- WDSPEngine.pas | 23 +++++++++++ 6 files changed, 159 insertions(+), 16 deletions(-) diff --git a/HPSDRNetwork.pas b/HPSDRNetwork.pas index 349503a..f5aa109 100644 --- a/HPSDRNetwork.pas +++ b/HPSDRNetwork.pas @@ -170,6 +170,7 @@ type FAlexConfig: TAlexSettings; // per-band antenna + routing config FStepAtten: Byte; // ADC0 step attenuator 0-31 dB FCWXKey: Boolean; // бит CWX (HP байт 5): программный ключ + FCWKeyerArmed: Boolean; // кейер прошивки вооружён (см. байт 345) // ---- OC Control (Open Collector выходы Penny/Alex, byte 1401) ---- FOCConfig: TOCSettings; FTuning: Boolean; // TUN активен (для TX pin action) @@ -271,6 +272,7 @@ type Tuning: Boolean = False; TwoTone: Boolean = False); override; procedure SetStepAtten(dB: Byte); override; procedure SetCWXKey(Down: Boolean); override; + procedure SetCWKeyerArmed(Armed: Boolean); override; procedure SetFreqCalPPM(P: Double); override; // Частота с поправкой опорника — для частотного слова DDC/DUC. function CalFreq(Hz: Double): Double; @@ -1812,6 +1814,13 @@ begin if FConnected then SendFullHP; end; +procedure THPSDRNetwork.SetCWKeyerArmed(Armed: Boolean); +begin + if FCWKeyerArmed = Armed then Exit; + FCWKeyerArmed := Armed; + if FConnected then SendFullHP; +end; + procedure THPSDRNetwork.SetStepAtten(dB: Byte); begin if dB > 31 then dB := 31; @@ -1899,8 +1908,11 @@ begin Buf[331] := (Ph shr 8) and $FF; Buf[332] := Ph and $FF; - // Drive level (byte 345) - if FIsTransmitting then + // Drive level (byte 345). Обычно уровень нужен только на передаче, но с + // вооружённым кейером телеграфа «передачи» в понимании софта не бывает: + // прошивка замыкает ключ сама, и в кадре, который она в этот момент видит, + // уровень уже обязан стоять — иначе посылка уходит с чужой мощностью. + if FIsTransmitting or FCWKeyerArmed then Buf[345] := FCurrentDrive; // ALEX0 filter bits (bytes 1432-1435) diff --git a/MainForm.pas b/MainForm.pas index ce1241b..67f6a28 100644 --- a/MainForm.pas +++ b/MainForm.pas @@ -1476,7 +1476,9 @@ begin FSpecView.TXMode := FController.FTransmitting and not FController.FDisplayDuplex; // Красная полоса главного VFO — только когда передаём с него: при // TX со слайса краснеет полоса ТОГО слайса (DrawSliceFilterMarkers). - FSpecView.TXOverlay := FController.FTransmitting and + // ★RadioKeyed, а не FTransmitting: в телеграфе ключ замыкает прошивка, + // софт «не передаёт», и полоса оставалась бы приёмной всю связь. + FSpecView.TXOverlay := FController.RadioKeyed and (FController.FTxSliceId = 0); PushFilterEdgesToView; // на передаче полоса рисуется по TX-фильтру ApplySpecViewGridFromState; // сетка спектра под активный тракт @@ -3711,7 +3713,9 @@ begin FSpecView.SMeterMin := FSMeterMin; FSpecView.LastFwdW := FController.FLastFwdW; FSpecView.LastSWR := FController.FLastSWR; - FSpecView.Transmitting := FController.FTransmitting; + // Метр — по RadioKeyed, а не по FTransmitting: в телеграфе софт «не передаёт», + // ключ замыкает прошивка, и стрелка осталась бы на S-метре всю связь. + FSpecView.Transmitting := FController.RadioKeyed; if PbSMeterRight <> nil then PbSMeterRight.Invalidate; // PWR/SWR метки и бары обновляем здесь (10 Гц), а не в DoUpdateStatus — @@ -8140,6 +8144,10 @@ procedure TMainForm.OnCWSettingsChange(const C: TCWSettings); // прошивку, персист — там же. Форме остаётся только передать запись. begin FController.SetCWSettings(C); + // Скорость кейера = ширина полосы телеграфа на экране. Движок вьюху сам не + // уведомляет — правило то же, что для профилей и кромок TX. + PushFilterEdgesToView; + if FController.FTransmitting then FSpectrumDirty := True; // Окно памяти показывает те же ячейки — держим его в курсе правок из настроек. if FCWMsgForm <> nil then TCWMessagesForm(FCWMsgForm).LoadFrom(FController.CWSettings); end; @@ -8170,17 +8178,20 @@ end; procedure TMainForm.FormKeyDownCW(Sender: TObject; var Key: Word; Shift: TShiftState); -// F1..F8 — ячейки памяти телеграфа. Работают только в CW и только когда окно -// памяти уже открывали (иначе клавиши остаются свободными для всего прочего). +// F1..F8 — ячейки памяти телеграфа. Единственное условие — телеграф на +// передающем источнике; в прочих режимах клавиши свободны для всего другого. +// ★Текст берём ИЗ НАСТРОЕК, а не из окна памяти: окно создаётся лениво, и +// раньше клавиши молчали, пока его хоть раз не открыли. var Slot: Integer; begin - if FCWMsgForm = nil then Exit; if Shift <> [] then Exit; - if not (FController.ActiveTXMode in [MODE_CWL, MODE_CWU]) then Exit; if (Key < VK_F1) or (Key > VK_F8) then Exit; + if not (FController.ActiveTXMode in [MODE_CWL, MODE_CWU]) then Exit; Slot := Key - VK_F1; - TCWMessagesForm(FCWMsgForm).SendSlot(Slot); + if (Slot < 0) or (Slot >= CW_MSG_COUNT) then Exit; Key := 0; + if FController.CWSettings.Messages[Slot] = '' then Exit; + FController.CWXSend(FController.CWSettings.Messages[Slot]); end; procedure TMainForm.OnAlexSettingsChange(const A: TAlexSettings); diff --git a/PanafallPanel.pas b/PanafallPanel.pas index 1580211..c0676cd 100644 --- a/PanafallPanel.pas +++ b/PanafallPanel.pas @@ -1366,7 +1366,7 @@ begin O.SetSquelchState(S.FMSQOn, S.FMSQLevel); // TxSel: слайс — источник передачи; Tx: передача идёт именно с него. O.SetExtState(Round(S.Volume * 100), S.Muted, False, - FController.FTransmitting and (FController.FTxSliceId = SliceId), + FController.RadioKeyed and (FController.FTxSliceId = SliceId), FController.FTxSliceId = SliceId); // Auto TX (настройка CAT-порта слайса) — бейдж TX подписан AutoTX. O.SetAutoTxState(FController.SliceAutoTx(SliceId)); @@ -1417,7 +1417,7 @@ begin // передаём со слайса, ActiveTXFreqHz берёт его частоту, а не VFO B. FMainFlag.SetExtState(FController.ActiveVolume, FController.FMuted, FController.FSplitTxB and (FController.FTxSliceId = 0), - FController.FTransmitting and (FController.FTxSliceId = 0), + FController.RadioKeyed and (FController.FTxSliceId = 0), FController.FTxSliceId = 0); PushFlagTXProfiles(FMainFlag, 0); end; diff --git a/RadioBackend.pas b/RadioBackend.pas index b456a71..dd39b69 100644 --- a/RadioBackend.pas +++ b/RadioBackend.pas @@ -184,6 +184,11 @@ type // передачи телеграфа — несущую по нему формирует прошивка. Пакет уходит // немедленно, каждый фронт (тайминги держит PC). Pluto — no-op. procedure SetCWXKey(Down: Boolean); virtual; + // Кейер в прошивке вооружён: железо может замкнуть ключ САМО, без нашего + // PTT. Пока флаг взведён, уровень мощности обязан лежать в каждом HP-кадре + // (обычно он шлётся только на передаче — а «передачи» с точки зрения софта + // тут не происходит вовсе). Pluto — no-op. + procedure SetCWKeyerArmed(Armed: Boolean); virtual; procedure SetStepAtten(dB: Byte); virtual; // RX-усиление (Pluto/AD936x). ModeIdx: 0=manual, 1=fast_attack, 2=slow_attack, // 3=hybrid. GainDb применяется только в manual. HPSDR — no-op. @@ -285,6 +290,10 @@ procedure TRadioBackend.SetCWXKey(Down: Boolean); begin end; +procedure TRadioBackend.SetCWKeyerArmed(Armed: Boolean); +begin +end; + procedure TRadioBackend.SetStepAtten(dB: Byte); begin end; diff --git a/RadioController.pas b/RadioController.pas index fd7b13d..9af8e2c 100644 --- a/RadioController.pas +++ b/RadioController.pas @@ -442,6 +442,14 @@ type // разбором HP-статуса (прямой ключ), читается DSP-потоком в MixSidetone — // толерантная гонка на одном булеве, как FHPp*. FCWKeyDown: Boolean; + // Прошлое состояние лепестков — обрыв программной передачи по фронту. + FCWPrevDot: Boolean; + FCWPrevDash: Boolean; + // Момент последнего замыкания ключа прошивкой — только для ИНДИКАЦИИ + // (см. RadioKeyed): без выдержки метр моргал бы RX/TX на каждой точке. + FCWKeyedAt: QWord; + // Предыдущее значение RadioKeyed — событие рендера шлём только на фронте. + FCWKeyedShown: Boolean; // Последний отправленный байт 5 — чтобы не гнать DUC-пакет на каждую // смену режима, а только когда телеграфная конфигурация реально поменялась. FLastCWOptions: Byte; @@ -945,6 +953,10 @@ type // ---- Телеграф ---------------------------------------------------------- // Настройки применяются целиком: pitch уезжает в движок (там он двигает // гетеродин и полосу), остальное — в DUC Specific прошивке. + // «Железо сейчас в эфире» — ДЛЯ ИНДИКАЦИИ (S-метр, лампочки), не для тракта. + // В телеграфе FTransmitting не поднимается: передачей владеет кейер прошивки, + // и единственный признак эфира — HPS_PTT в статусе. + function RadioKeyed: Boolean; function CWSettings: TCWSettings; procedure SetCWSettings(const C: TCWSettings); // Программная передача текста: PC сам выдерживает тайминги и дёргает бит @@ -1495,11 +1507,16 @@ begin FHPpHWPTT := (Status.StatusBits and HPS_PTT) <> 0; FHPpOverload := Status.ADCOverload; // Состояние ключа/манипулятора в разъёме трансивера (сетевой поток). - // Любое касание обрывает программную передачу — как в Thetis/pihpsdr - // («key hit» останавливает CAT CW), иначе оператор не может вклиниться. + // Касание обрывает программную передачу — как в Thetis/pihpsdr («key hit» + // останавливает CAT CW), иначе оператор не может вклиниться. ★Обрываем по + // ФРОНТУ, а не по уровню: на плате без подключённого ключа входы могут стоять + // в единице, и проверка уровня убивала бы передачу на первом же HP-статусе. KeyDot := (Status.StatusBits and HPS_DOT) <> 0; KeyDash := (Status.StatusBits and HPS_DASH) <> 0; - if (KeyDot or KeyDash) and CWXBusy then CWXAbort; + if ((KeyDot and not FCWPrevDot) or (KeyDash and not FCWPrevDash)) + and CWXBusy then CWXAbort; + FCWPrevDot := KeyDot; + FCWPrevDash := KeyDash; // Программный сайдтон от лепестков верен ТОЛЬКО у прямого ключа: при иамбике // элементы считает FPGA, а PC видит лишь замыкание контакта. if (FCWSettings.KeyerMode = 0) and (ActiveTXMode in [MODE_CWL, MODE_CWU]) @@ -1553,6 +1570,32 @@ begin Changed(rfADCOverload); end; + // ★Телеграф с кейером в прошивке: HPS_PTT здесь — ЭХО собственной манипуляции + // (при break-in железо само поднимает T/R на каждую посылку), а не действие + // оператора. Гонять по нему SetMOX нельзя: на каждой точке пересобирался бы + // передающий тракт, а программная передача обрывалась бы после ПЕРВОЙ посылки + // (снятие MOX гасит CWX). Флаг держим актуальным — им выбирается источник + // модуляции, — но передачей по нему не управляем. + if CWTXActive then + begin + FHWPTTActive := FHPpHWPTT; + if FHPpHWPTT then FCWKeyedAt := GetTickCount64; + // UI про телеграфную передачу иначе не узнает вовсе: SetMOX здесь не + // вызывается, а значит и rfTransmitting никто не шлёт. Фронт RadioKeyed + // (с выдержкой) — единственный сигнал «покрасить бейджи и полосы». + if FCWKeyedShown <> RadioKeyed then + begin + FCWKeyedShown := RadioKeyed; + Changed(rfTransmitting); + end; + Exit; + end; + // Ушли из телеграфа/сняли вооружение — вернуть индикацию в исходное. + if FCWKeyedShown then + begin + FCWKeyedShown := False; + Changed(rfTransmitting); + end; // Hardware PTT (footswitch / mic PTT): фронт на rate HP Status — не пропустить // короткое нажатие. HWPTT не перекрывает ручной MOX; выключаем TX только если // его включил именно HWPTT. @@ -2291,6 +2334,10 @@ begin FSlices[idx].TargetHz := TargetHz; if Assigned(FDSPEngine) then FDSPEngine.SetSliceShift(Id, TargetHz - PanCenterHz(FSlices[idx].PanId)); + // Слайс — источник передачи ⇒ он же задаёт частоту DUC. Раньше её дошлёт бы + // первый MOX, но в телеграфе ключ замыкает прошивка без всякого MOX: DUC + // обязан стоять правильно ВСЕГДА, а не только на передаче. + if FTxSliceId = Id then PushNetworkState; end; procedure TRadioController.SetSliceMode(Id, Mode: Integer); @@ -5628,6 +5675,23 @@ end; // Телеграф // =========================================================================== +function TRadioController.RadioKeyed: Boolean; +// Выдержка после последней посылки: точка на 20 WPM длится 60 мс, а индикаторы +// перерисовываются 10 раз в секунду — без неё бейджи и метр мигали бы внутри +// одного знака. Держим 8 точек, но не меньше 500 мс: на медленной скорости +// межсловный интервал (7 точек) сам по себе длиннее полусекунды, и фиксированная +// выдержка гасила бы индикацию между словами. +var Hold: QWord; +begin + Result := FTransmitting or FTuning; + if Result then Exit; + if not CWTXActive then Exit(False); + if FCWKeyedAt = 0 then Exit(False); + Hold := 8 * (1200 div EnsureRange(FCWSettings.Speed, 5, 60)); + if Hold < 500 then Hold := 500; + Result := (GetTickCount64 - FCWKeyedAt) < Hold; +end; + function TRadioController.CWSettings: TCWSettings; begin Result := FCWSettings; @@ -5645,10 +5709,14 @@ begin FCWSettings.SidetoneHWLevel := EnsureRange(FCWSettings.SidetoneHWLevel, 0, 127); FCWSettings.SidetoneSWLevel := EnsureRange(FCWSettings.SidetoneSWLevel, 0, 100); if FWDSPReady and Assigned(FDSPEngine) then + begin FDSPEngine.SetCWPitch(FCWSettings.Pitch); + FDSPEngine.SetCWSpeed(FCWSettings.Speed); // ширина полосы CW на экране + end; if Assigned(FCWSender) then FCWSender.SetSpeed(FCWSettings.Speed, FCWSettings.Weight); SendDUCSpecificFromSettings; + if Assigned(FNetwork) then FNetwork.SetCWKeyerArmed(CWTXActive); // Тон TUN в телеграфе равен pitch (несущая обязана лечь на VFO, см. SetTune): // если крутят pitch прямо на настройке — обновляем живьём. if FTuning and FWDSPReady and Assigned(FDSPEngine) @@ -5667,6 +5735,9 @@ procedure TRadioController.SyncCWKeyer; // иначе прошивка поднимет PTT на замыкание ключа посреди SSB. begin if not (ActiveTXMode in [MODE_CWL, MODE_CWU]) then CWXAbort; + // Вооружённый кейер = железо может выйти в эфир само ⇒ бэкенд обязан класть + // уровень мощности в КАЖДЫЙ HP-кадр, а не только на передаче. + if Assigned(FNetwork) then FNetwork.SetCWKeyerArmed(CWTXActive); if CWOptionsByte <> FLastCWOptions then SendDUCSpecificFromSettings; end; @@ -6224,8 +6295,17 @@ begin // Сначала отправляем Run=1 — эмулятор/железо создают ddc_specific_thread // (порт 1025) и rx_thread (порт 1035+) только после получения HP Run=1. // DDC Specific и DUC Specific отправляем ПОСЛЕ, иначе порты ещё не открыты. + // Уровень пересчитываем здесь же: с вооружённым кейером он уедет в железо + // сразу, до всякой передачи, а после конструктора FDriveLevel ещё нулевой. + FDriveLevel := CalcDriveByte; FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel, False, True, True, FTuning, FTwoTone); - FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), FDriveLevel); + // ★DUC — на TX-ЧАСТОТУ, а не на центр дисплея. Раньше здесь стоял центр, и до + // первой перестройки VFO (или до первого MOX, который частоту дошлёт сам) + // передатчик стоял не там, где маркер: при CTUN центр и VFO — разные частоты. + // С кейером в прошивке это стало слышно в эфире: железо может замкнуть ключ + // сразу после старта, никакого MOX перед этим не будет. + FNetwork.SetRunAndFreq(True, XvtrTranslate(FCenterFreq), + XvtrTranslateTX(ActiveTXFreqHz), FDriveLevel); FRunning := True; FRXStartTime := GetTickCount64; FRXLastPktTime := 0; @@ -6251,8 +6331,12 @@ begin // Тон телеграфа в движок: на момент ConnectDevice канал WDSP мог быть ещё // не открыт, а здесь он гарантированно жив. if FWDSPReady and Assigned(FDSPEngine) then + begin FDSPEngine.SetCWPitch(FCWSettings.Pitch); + FDSPEngine.SetCWSpeed(FCWSettings.Speed); + end; SendDUCSpecificFromSettings; + SyncCWKeyer; // вооружение кейера + уровень мощности в HP-кадре // Применяем TX-цепь (фильтр, mic gain, EQ, leveler, ALC, comp, ...). ApplyTXSettingsToDSP; ApplyModeFilter; @@ -6280,7 +6364,8 @@ begin for p := 1 to MAX_PANS - 1 do RemovePanDDC(p); FNetwork.UpdateState(XvtrTranslate(FCenterFreq), XvtrTranslateTX(ActiveTXFreqHz), 0, False, True, True, FTuning, FTwoTone); - FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), XvtrTranslate(FCenterFreq), 0); + FNetwork.SetRunAndFreq(False, XvtrTranslate(FCenterFreq), + XvtrTranslateTX(ActiveTXFreqHz), 0); FRunning := False; FTransmitting := False; // Стоп при активном TX/TUN: хвост IQ в DUC-очереди не должен пережить @@ -6465,7 +6550,10 @@ begin FSettings.LoadTX(FDevMAC, FTXSettings); FSettings.LoadCW(FDevMAC, FCWSettings); if FWDSPReady and Assigned(FDSPEngine) then + begin FDSPEngine.SetCWPitch(FCWSettings.Pitch); + FDSPEngine.SetCWSpeed(FCWSettings.Speed); + end; FSettings.LoadAlex(FDevMAC, FAlexSettings); FSettings.LoadAnt936x(FDevMAC, FAnt936x); FNetwork.SetAlexConfig(FAlexSettings); diff --git a/WDSPEngine.pas b/WDSPEngine.pas index 03f6f86..03d8751 100644 --- a/WDSPEngine.pas +++ b/WDSPEngine.pas @@ -473,6 +473,7 @@ type FDisplayResLimit: Integer; // потолок точек анализатора (настройка display_pixels) FShiftHz: Double; // NCO shift для CTUN FCWPitch: Integer; // Гц, тон телеграфа (см. CWLOOffset) + FCWSpeed: Integer; // WPM — только для занимаемой полосы на экране FNRMode: Integer; FNBMode: Integer; FSNBEnabled: Boolean; @@ -654,6 +655,9 @@ type // применяется немедленно (главный + слайсы), персист делает контроллер. procedure SetCWPitch(Hz: Integer); function CWPitch: Integer; + // Скорость кейера нужна движку ровно для одного — ширины полосы телеграфа + // на экране (TXFilterEdgesHz). Тайминги делает прошивка, не мы. + procedure SetCWSpeed(WPM: Integer); // Смещение гетеродина демодулятора относительно VFO в телеграфе (см. // комментарий у PushShift). Публично: тем же сдвигом контроллер двигает // DUC при настройке TUN, чтобы несущая легла ровно на VFO. @@ -1370,6 +1374,7 @@ begin FAGCMode := agcMedium; FShiftHz := 0.0; FCWPitch := 600; + FCWSpeed := 20; FAGCTop := -90.0; FVolume := 0.7; FSMeter := -130.0; @@ -3090,6 +3095,13 @@ begin end; end; +procedure TWDSPEngine.SetCWSpeed(WPM: Integer); +begin + if WPM < 5 then WPM := 5; + if WPM > 60 then WPM := 60; + FCWSpeed := WPM; +end; + procedure TWDSPEngine.SetAGCTop(TopDBm: Double); begin FAGCTop := TopDBm; @@ -3821,6 +3833,17 @@ begin MODE_FM: Half := FTXFMDeviation + FTXFMHighCut; MODE_WFM: Half := WFM_DEVIATION + WFM_AF_HIGH; MODE_FMRAW: Half := FRawFMDeviation + RAW_AF_HIGH; + // ★Телеграф. TXSignedEdges отдаёт для CWL/CWU ГОЛОСОВУЮ боковую (bp0 обязан + // оставаться открытым: через него идёт тон pitch при настройке TUN), но в + // эфир уходит манипулируемая НЕСУЩАЯ на самом VFO. Рисовать её юбкой SSB + // нельзя — полоса выглядела как в SSB и уезжала вбок от несущей. + // Занимаемая полоса телеграфа: B = WPM/1.2 бод, BW ≈ 5·B при мягкой + // манипуляции (фронт CWRampPeriod). Симметрично несущей. + MODE_CWL, MODE_CWU: + begin + Half := 5.0 * (FCWSpeed / 1.2) / 2.0; + if Half < 25.0 then Half := 25.0; // не вырождаться в нулевую полоску + end; end; if Half > 0.0 then begin