mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 20:37:33 +00:00
feat(cw): терминал телеграфа — набор с клавиатуры и декодер приёма
Окно (ПКМ по CWL/CWU, F9, кнопка в настройках CW): одна лента, где принятое декодером и СВОЯ передача идут вперемешку — своё акцентным цветом. Разделять их нельзя: в QSK они перемежаются посреди фразы. Под лентой строка набора. Печать уходит в эфир ПОСИМВОЛЬНО, а не по Enter: строка показывает ровно то, что ещё не передано (очередь генератора), знаки уходят из неё по мере отправки, Backspace стирает с хвоста очереди — то, что уже звучит, вернуть нельзя. Ctrl работает манипулятором (левый точка, правый тире; у кейера прошивки лепестков нет — там это прямой ключ через бит CWX), по умолчанию выключено, чтобы Ctrl+C не уводил в эфир. Отпускание ключа ловится и на потере фокуса: иначе уход из окна с зажатым Ctrl оставил бы несущую в эфире навсегда. CWDecoder.pas: аудио → текст. Отвод берётся до громкости и мьюта — там нет программного сайдтона, зато уже отработал узкий CW-фильтр, лучшего предетектора не найти. Гёрцель гребёнкой из пяти бинов вокруг pitch (заодно показывает расстройку) → огибающая → адаптивный порог с гистерезисом → длительности → адаптивная точка → обратная таблица Морзе. Длительности живут в шагах анализа, а не в показаниях часов: разбор идёт пачками с таймера, и привязка ко времени вызова ломала бы тайминг на любой загрузке. Вылезло на тестах и учтено: пик обязан клампиться не ниже пола шума (иначе на старте порог уходит НИЖЕ шума и первым «знаком» читается собственный шум); порог дребезга берётся от текущей точки, фиксированный либо пропускает щелчки на медленной передаче, либо ест посылки на быстрой; длина посылки меряется за вычетом подтверждения дребезга, иначе скорость занижалась на 15%; расстройка запоминается только на полной амплитуде посылки, иначе индикатор пляшет. Граница честная: при вдвое неверной подсказке скорости теряется первое слово — пока не услышана настоящая точка, длина элемента неизвестна. Лента расшифровки продублирована строкой под спектром (пан 0), эхо передачи и очередь набора появились у обоих отправителей — и у локального генератора, и у кейера прошивки. ★TFlatEdit получил публичный CaretPos: он вставляет знак сам в UTF8KeyPress и гасит клавишу, поэтому OnKeyPress контрола не вызывается вовсе — из-за этого набранное «исчезало», а в эфир не уходило. Ввод перенесён на уровень формы. Проверено оффлайн: 12/20/40 WPM, расстройка, шум, слабый сигнал, цифры и знаки; плюс сквозной прогон против шести станций CW-стенда в hpsdrsim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+34
-16
@@ -61,6 +61,7 @@ type
|
||||
TOnTXSettingsChange = procedure(const T: TTXSettings) of object;
|
||||
TOnCWSettingsChange = procedure(const C: TCWSettings) of object;
|
||||
TOnCWMessagesOpen = procedure of object;
|
||||
TOnCWTerminalOpen = procedure of object;
|
||||
// TX-профили: выбор/переименование/удаление по индексу, создание — по имени.
|
||||
TOnTXProfileIdx = procedure(Idx: Integer) of object;
|
||||
TOnTXProfileName = procedure(const AName: string) of object;
|
||||
@@ -312,6 +313,8 @@ type
|
||||
FEdCWKeyPort: TFlatEdit;
|
||||
FCmbCWDotLine: TFlatComboBox;
|
||||
FCmbCWDashLine: TFlatComboBox;
|
||||
FChkCWDecoder: TFlatCheckBox;
|
||||
FChkCWDecStrip: TFlatCheckBox;
|
||||
FChkCWKeyInvert: TFlatCheckBox;
|
||||
FChkCWKeyDTR: TFlatCheckBox;
|
||||
FChkCWKeyRTS: TFlatCheckBox;
|
||||
@@ -479,6 +482,7 @@ type
|
||||
FOnTXChange: TOnTXSettingsChange;
|
||||
FOnCWChange: TOnCWSettingsChange;
|
||||
FOnCWMessagesOpen: TOnCWMessagesOpen;
|
||||
FOnCWTerminalOpen: TOnCWTerminalOpen;
|
||||
FOnTXProfileSelect: TOnTXProfileIdx;
|
||||
FOnTXProfileAdd: TOnTXProfileName;
|
||||
FOnTXProfileRename: TOnTXProfileIdxName;
|
||||
@@ -537,6 +541,7 @@ type
|
||||
procedure FireCWChange;
|
||||
procedure OnCWAnyChange(Sender: TObject);
|
||||
procedure BtnCWMessagesClick(Sender: TObject);
|
||||
procedure BtnCWTerminalClick(Sender: TObject);
|
||||
procedure OnMicJackChange(Sender: TObject);
|
||||
procedure UpdateMicJackVisibility;
|
||||
// Подвкладки и полоса профиля на странице Transmit
|
||||
@@ -720,6 +725,7 @@ type
|
||||
property OnTXChange: TOnTXSettingsChange read FOnTXChange write FOnTXChange;
|
||||
property OnCWChange: TOnCWSettingsChange read FOnCWChange write FOnCWChange;
|
||||
property OnCWMessagesOpen: TOnCWMessagesOpen read FOnCWMessagesOpen write FOnCWMessagesOpen;
|
||||
property OnCWTerminalOpen: TOnCWTerminalOpen read FOnCWTerminalOpen write FOnCWTerminalOpen;
|
||||
property OnTXProfileSelect: TOnTXProfileIdx read FOnTXProfileSelect write FOnTXProfileSelect;
|
||||
property OnTXProfileAdd: TOnTXProfileName read FOnTXProfileAdd write FOnTXProfileAdd;
|
||||
property OnTXProfileRename: TOnTXProfileIdxName read FOnTXProfileRename write FOnTXProfileRename;
|
||||
@@ -2056,15 +2062,16 @@ begin
|
||||
|
||||
// ---- CW: телеграфом в openHPSDR P2 управляет прошивка -------------------
|
||||
Inc(Y, 190 + 12);
|
||||
Grp := MakeGroupPanel(FTXSubPage[2], 'CW (telegraphy)', MARGIN, Y, GRP_W, 460);
|
||||
Grp := MakeGroupPanel(FTXSubPage[2], 'CW (telegraphy)', MARGIN, Y, GRP_W, 502);
|
||||
|
||||
MakeLbl(Grp, 'Keyer', PAD, R1 - 1, 50);
|
||||
FCmbCWKeyerSrc := MkCmbCW(Grp, PAD + 54, R1 - 6, 194);
|
||||
FCmbCWKeyerSrc.Items.AddStrings(['Radio firmware','Software (PC)','Off (MOX only)']);
|
||||
FCmbCWKeyerSrc.ItemIndex := 0;
|
||||
MkBtn(Grp, PAD + 260, R1 - 6, 110, 'Messages...', BtnCWMessagesClick);
|
||||
MakeLbl(Grp, 'Pitch (Hz)', PAD + 400, R1 - 1, 90);
|
||||
FEdCWPitch := MkSpinCW(Grp, PAD + 500, R1 - 6, 200, 1200, 600);
|
||||
MkBtn(Grp, PAD + 260, R1 - 6, 100, 'Messages...', BtnCWMessagesClick);
|
||||
MkBtn(Grp, PAD + 366, R1 - 6, 100, 'Terminal...', BtnCWTerminalClick);
|
||||
MakeLbl(Grp, 'Pitch (Hz)', PAD + 480, R1 - 1, 90);
|
||||
FEdCWPitch := MkSpinCW(Grp, PAD + 574, R1 - 6, 200, 1200, 600);
|
||||
|
||||
MakeLbl(Grp, 'Keyer mode', PAD, R1 + STEP + 5, LW);
|
||||
FCmbCWKeyerMode := MkCmbCW(Grp, CX, R1 + STEP, 200);
|
||||
@@ -2097,29 +2104,31 @@ begin
|
||||
MakeLbl(Grp, 'Carrier offset (Hz)', PAD, R1 + 5 * STEP + 5, LW);
|
||||
FEdCWOffset := MkSpinCW(Grp, CX, R1 + 5 * STEP, -1, 48000, 0);
|
||||
MakeLbl(Grp, '0 = auto, -1 = none (software keyer only)', PAD + 320, R1 + 5 * STEP + 5, 260);
|
||||
FChkCWDecoder := MkChkCW(Grp, PAD, R1 + 6 * STEP, 160, 'Decode RX');
|
||||
FChkCWDecStrip := MkChkCW(Grp, PAD + 176, R1 + 6 * STEP, 210, 'Text under spectrum');
|
||||
|
||||
FChkCWKeyPort := MkChkCW(Grp, PAD, R1 + 6 * STEP, 250, 'Paddle on serial port');
|
||||
FChkCWKeyPort := MkChkCW(Grp, PAD, R1 + 7 * STEP, 250, 'Paddle on serial port');
|
||||
FEdCWKeyPort := TFlatEdit.Create(Self);
|
||||
FEdCWKeyPort.Parent := Grp;
|
||||
FEdCWKeyPort.SetBounds(DpiScale(PAD + 260), DpiScale(R1 + 6 * STEP),
|
||||
FEdCWKeyPort.SetBounds(DpiScale(PAD + 260), DpiScale(R1 + 7 * STEP),
|
||||
DpiScale(220), DpiScale(BTN_H));
|
||||
FEdCWKeyPort.Color := CLR_INPUT;
|
||||
FEdCWKeyPort.Font.Color := CLR_INPUT_TEXT;
|
||||
FEdCWKeyPort.Font.Size := 9;
|
||||
FEdCWKeyPort.OnExit := OnCWAnyChange;
|
||||
MakeLbl(Grp, '/dev/ttyUSB0, COM3', PAD + 492, R1 + 6 * STEP + 5, 200);
|
||||
MakeLbl(Grp, '/dev/ttyUSB0, COM3', PAD + 492, R1 + 7 * STEP + 5, 200);
|
||||
|
||||
MakeLbl(Grp, 'Dot line', PAD, R1 + 7 * STEP + 5, 70);
|
||||
FCmbCWDotLine := MkCmbCW(Grp, PAD + 74, R1 + 7 * STEP, 90);
|
||||
MakeLbl(Grp, 'Dot line', PAD, R1 + 8 * STEP + 5, 70);
|
||||
FCmbCWDotLine := MkCmbCW(Grp, PAD + 74, R1 + 8 * STEP, 90);
|
||||
FCmbCWDotLine.Items.AddStrings(['CTS','DSR','DCD','RI']);
|
||||
FCmbCWDotLine.ItemIndex := 0;
|
||||
MakeLbl(Grp, 'Dash line', PAD + 176, R1 + 7 * STEP + 5, 74);
|
||||
FCmbCWDashLine := MkCmbCW(Grp, PAD + 254, R1 + 7 * STEP, 90);
|
||||
MakeLbl(Grp, 'Dash line', PAD + 176, R1 + 8 * STEP + 5, 74);
|
||||
FCmbCWDashLine := MkCmbCW(Grp, PAD + 254, R1 + 8 * STEP, 90);
|
||||
FCmbCWDashLine.Items.AddStrings(['CTS','DSR','DCD','RI']);
|
||||
FCmbCWDashLine.ItemIndex := 1;
|
||||
FChkCWKeyInvert := MkChkCW(Grp, PAD + 360, R1 + 7 * STEP, 90, 'Invert');
|
||||
FChkCWKeyDTR := MkChkCW(Grp, PAD + 456, R1 + 7 * STEP, 100, 'DTR power');
|
||||
FChkCWKeyRTS := MkChkCW(Grp, PAD + 562, R1 + 7 * STEP, 100, 'RTS power');
|
||||
FChkCWKeyInvert := MkChkCW(Grp, PAD + 360, R1 + 8 * STEP, 90, 'Invert');
|
||||
FChkCWKeyDTR := MkChkCW(Grp, PAD + 456, R1 + 8 * STEP, 100, 'DTR power');
|
||||
FChkCWKeyRTS := MkChkCW(Grp, PAD + 562, R1 + 8 * STEP, 100, 'RTS power');
|
||||
|
||||
TxNote(Grp, 'Firmware keyer: the radio produces dot and dash timing, so PC '
|
||||
+ 'load never affects it — the default where the hardware has one, and the '
|
||||
@@ -2130,9 +2139,9 @@ begin
|
||||
+ 'offset 0 means auto: zero on openHPSDR, 12 kHz on zero-IF hardware, '
|
||||
+ 'where LO leakage would otherwise sit right on the working frequency '
|
||||
+ 'between elements. The voice transmit chain is not started in CW at all.',
|
||||
R1 + 8 * STEP - 6, 92);
|
||||
R1 + 9 * STEP - 6, 92);
|
||||
|
||||
Inc(Y, 460 + 12);
|
||||
Inc(Y, 502 + 12);
|
||||
Grp := MakeGroupPanel(FTXSubPage[2], 'FM / CTCSS', MARGIN, Y, GRP_W, 250);
|
||||
|
||||
MakeLbl(Grp, 'FM deviation (Hz)', PAD, R1 + 5, LW);
|
||||
@@ -2355,6 +2364,8 @@ begin
|
||||
if FEdCWKeyPort <> nil then FCW.KeyPort := Copy(FEdCWKeyPort.Text, 1, 63);
|
||||
if FCmbCWDotLine <> nil then FCW.KeyDotLine := EnsureRange(FCmbCWDotLine.ItemIndex, 0, 3);
|
||||
if FCmbCWDashLine <> nil then FCW.KeyDashLine := EnsureRange(FCmbCWDashLine.ItemIndex, 0, 3);
|
||||
if FChkCWDecoder <> nil then FCW.Decoder := FChkCWDecoder.Checked;
|
||||
if FChkCWDecStrip <> nil then FCW.DecoderStrip := FChkCWDecStrip.Checked;
|
||||
if FChkCWKeyInvert <> nil then FCW.KeyInvert := FChkCWKeyInvert.Checked;
|
||||
if FChkCWKeyDTR <> nil then FCW.KeyPowerDTR := FChkCWKeyDTR.Checked;
|
||||
if FChkCWKeyRTS <> nil then FCW.KeyPowerRTS := FChkCWKeyRTS.Checked;
|
||||
@@ -2399,6 +2410,11 @@ begin
|
||||
if Assigned(FOnCWMessagesOpen) then FOnCWMessagesOpen;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.BtnCWTerminalClick(Sender: TObject);
|
||||
begin
|
||||
if Assigned(FOnCWTerminalOpen) then FOnCWTerminalOpen;
|
||||
end;
|
||||
|
||||
procedure TSettingsForm.LoadCWSettings(const C: TCWSettings);
|
||||
var Src: Integer;
|
||||
begin
|
||||
@@ -2434,6 +2450,8 @@ begin
|
||||
if FEdCWKeyPort <> nil then FEdCWKeyPort.Text := C.KeyPort;
|
||||
if FCmbCWDotLine <> nil then FCmbCWDotLine.ItemIndex := EnsureRange(C.KeyDotLine, 0, 3);
|
||||
if FCmbCWDashLine <> nil then FCmbCWDashLine.ItemIndex := EnsureRange(C.KeyDashLine, 0, 3);
|
||||
if FChkCWDecoder <> nil then FChkCWDecoder.Checked := C.Decoder;
|
||||
if FChkCWDecStrip <> nil then FChkCWDecStrip.Checked := C.DecoderStrip;
|
||||
if FChkCWKeyInvert <> nil then FChkCWKeyInvert.Checked := C.KeyInvert;
|
||||
if FChkCWKeyDTR <> nil then FChkCWKeyDTR.Checked := C.KeyPowerDTR;
|
||||
if FChkCWKeyRTS <> nil then FChkCWKeyRTS.Checked := C.KeyPowerRTS;
|
||||
|
||||
Reference in New Issue
Block a user