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:
+86
-1
@@ -37,6 +37,10 @@ type
|
||||
FBusy: Boolean; // идёт передача (под FLock)
|
||||
FWPM: Integer; // под FLock
|
||||
FWeight: Integer; // 33..66, 50 = симметрично (под FLock)
|
||||
// Эхо для окна-терминала: ушедшее в эфир и хвост очереди (под FLock).
|
||||
FSent: string;
|
||||
FUnsent: string;
|
||||
FTrimTail: Boolean; // Backspace дотянулся до уже взятого текста
|
||||
function TakeText: string;
|
||||
function Aborted: Boolean;
|
||||
procedure Key(Down: Boolean);
|
||||
@@ -53,6 +57,11 @@ type
|
||||
procedure AbortSending;
|
||||
procedure SetSpeed(WPM, Weight: Integer);
|
||||
function Busy: Boolean;
|
||||
// Терминал: знаки, ушедшие в эфир с прошлого опроса; хвост очереди; стереть
|
||||
// последний ненабранный знак. Смысл тот же, что у TCWLocalKeyer.
|
||||
function TakeSentText: string;
|
||||
function PendingText: string;
|
||||
function Backspace: Boolean;
|
||||
end;
|
||||
|
||||
// Код знака: строка из '.' и '-'. Пустая — знака нет в таблице (пропускаем,
|
||||
@@ -163,6 +172,49 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWSender.TakeSentText: string;
|
||||
begin
|
||||
FLock.Enter;
|
||||
try
|
||||
Result := FSent;
|
||||
FSent := '';
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWSender.PendingText: string;
|
||||
begin
|
||||
FLock.Enter;
|
||||
try
|
||||
Result := FUnsent + FPending;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWSender.Backspace: Boolean;
|
||||
// Стираем с хвоста очереди: то, что уже звучит, вернуть нельзя.
|
||||
begin
|
||||
FLock.Enter;
|
||||
try
|
||||
Result := False;
|
||||
if FPending <> '' then
|
||||
begin
|
||||
SetLength(FPending, Length(FPending) - 1);
|
||||
Result := True;
|
||||
end
|
||||
else if FUnsent <> '' then
|
||||
begin
|
||||
SetLength(FUnsent, Length(FUnsent) - 1);
|
||||
FTrimTail := True;
|
||||
Result := True;
|
||||
end;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWSender.TakeText: string;
|
||||
begin
|
||||
FLock.Enter;
|
||||
@@ -252,7 +304,8 @@ end;
|
||||
procedure TCWSender.Execute;
|
||||
var
|
||||
Text: string;
|
||||
i: Integer;
|
||||
i, KeepLen: Integer;
|
||||
Trim: Boolean;
|
||||
Deadline: QWord;
|
||||
begin
|
||||
while not Terminated do
|
||||
@@ -275,6 +328,31 @@ begin
|
||||
while i <= Length(Text) do
|
||||
begin
|
||||
if Terminated or Aborted then Break;
|
||||
// Backspace мог съесть хвост уже взятого текста — отсекаем.
|
||||
FLock.Enter;
|
||||
try
|
||||
Trim := FTrimTail;
|
||||
FTrimTail := False;
|
||||
KeepLen := i - 1 + Length(FUnsent);
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
if Trim then
|
||||
begin
|
||||
if KeepLen < i then Break; // стёрли всё впереди
|
||||
if Length(Text) > KeepLen then SetLength(Text, KeepLen);
|
||||
if i > Length(Text) then Break;
|
||||
end;
|
||||
// Эхо в окно-терминал: знак пошёл в эфир; хвост очереди — для строки
|
||||
// набора (там должно остаться только НЕ переданное).
|
||||
FLock.Enter;
|
||||
try
|
||||
FSent := FSent + Text[i];
|
||||
FUnsent := Copy(Text, i + 1, MaxInt);
|
||||
if Length(FSent) > 4096 then FSent := Copy(FSent, Length(FSent) - 4095, 4096);
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
SendChar(Text[i], Deadline);
|
||||
// Дописанное в очередь по ходу передачи подхватываем без паузы —
|
||||
// иначе поток допередал бы «старый» текст и уснул на 200 мс.
|
||||
@@ -284,6 +362,13 @@ begin
|
||||
finally
|
||||
Key(False); // ключ всегда отпущен, что бы ни случилось
|
||||
FLock.Enter;
|
||||
try
|
||||
FUnsent := '';
|
||||
FTrimTail := False;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
FLock.Enter;
|
||||
try
|
||||
FBusy := False;
|
||||
finally
|
||||
|
||||
Reference in New Issue
Block a user