mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 17:27:32 +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:
+76
-1
@@ -99,6 +99,11 @@ type
|
||||
FDashIn: Boolean;
|
||||
FStraightIn:Boolean; // прямой ключ отдельным источником
|
||||
FTouched: Boolean; // касание манипулятора — обрывает текст
|
||||
// Эхо передачи для окна-терминала: знаки, которые УЖЕ ушли в эфир, и хвост,
|
||||
// который ещё стоит в очереди. Ведём под FLock, потому что читает их UI.
|
||||
FSent: string;
|
||||
FUnsent: string;
|
||||
FTrimTail: Boolean; // Backspace съел знак из уже разобранного текста
|
||||
// ---- рабочая копия (только поток генератора) ---------------------------
|
||||
FRate: Integer;
|
||||
FAmp: Double;
|
||||
@@ -170,6 +175,12 @@ type
|
||||
procedure SendText(const S: string);
|
||||
procedure AbortText;
|
||||
function Busy: Boolean;
|
||||
// Терминал: знаки, ушедшие в эфир с прошлого опроса (эхо своей передачи).
|
||||
function TakeSentText: string;
|
||||
// Терминал: что ещё стоит в очереди (набранное вперёд).
|
||||
function PendingText: string;
|
||||
// Стереть последний НЕ ушедший знак (Backspace в окне набора).
|
||||
function Backspace: Boolean;
|
||||
|
||||
// Вход манипулятора (поток опроса порта / GUI). Состояния лепестков сырые,
|
||||
// Reverse применяет сам генератор.
|
||||
@@ -357,6 +368,52 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWLocalKeyer.TakeSentText: string;
|
||||
begin
|
||||
FLock.Enter;
|
||||
try
|
||||
Result := FSent;
|
||||
FSent := '';
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWLocalKeyer.PendingText: string;
|
||||
begin
|
||||
FLock.Enter;
|
||||
try
|
||||
Result := FUnsent + FPending;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TCWLocalKeyer.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
|
||||
// Хвост уже разобранного текста: помечаем к отсечению — генератор увидит
|
||||
// укороченный FUnsent на следующем знаке (см. TakeTextElement).
|
||||
SetLength(FUnsent, Length(FUnsent) - 1);
|
||||
FTrimTail := True;
|
||||
Result := True;
|
||||
end;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TCWLocalKeyer.Paddle(Dot, Dash: Boolean);
|
||||
begin
|
||||
FLock.Enter;
|
||||
@@ -612,6 +669,8 @@ begin
|
||||
FLock.Enter;
|
||||
try
|
||||
FBusyText := False;
|
||||
FUnsent := '';
|
||||
FTrimTail := False;
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
@@ -625,7 +684,8 @@ function TCWLocalKeyer.TakeTextElement(out IsDot: Boolean): Boolean;
|
||||
var
|
||||
Ch: Char;
|
||||
Add: string;
|
||||
Drop: Boolean;
|
||||
Drop, Trim: Boolean;
|
||||
KeepLen: Integer;
|
||||
begin
|
||||
Result := False;
|
||||
IsDot := True;
|
||||
@@ -642,6 +702,9 @@ begin
|
||||
end;
|
||||
Add := FPending;
|
||||
FPending := '';
|
||||
Trim := FTrimTail;
|
||||
FTrimTail := False;
|
||||
KeepLen := FTextPos + Length(FUnsent);
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
@@ -650,6 +713,8 @@ begin
|
||||
ResetText;
|
||||
Exit;
|
||||
end;
|
||||
// Backspace дотянулся до уже разобранного текста — отсекаем хвост.
|
||||
if Trim and (Length(FText) > KeepLen) then SetLength(FText, KeepLen);
|
||||
if Add <> '' then FText := FText + Add;
|
||||
|
||||
// Ищем следующий элемент, пропуская пробелы и знаки не из таблицы.
|
||||
@@ -662,6 +727,16 @@ begin
|
||||
end;
|
||||
Inc(FTextPos);
|
||||
Ch := FText[FTextPos];
|
||||
// Эхо: знак пошёл в эфир — отдаём его окну-терминалу, а хвост очереди
|
||||
// обновляем, чтобы строка набора показывала только ненабранное.
|
||||
FLock.Enter;
|
||||
try
|
||||
FSent := FSent + Ch;
|
||||
FUnsent := Copy(FText, FTextPos + 1, MaxInt);
|
||||
if Length(FSent) > 4096 then FSent := Copy(FSent, Length(FSent) - 4095, 4096);
|
||||
finally
|
||||
FLock.Leave;
|
||||
end;
|
||||
if Ch = ' ' then
|
||||
begin
|
||||
// 3 точки уже отданы после прошлого знака — добираем до 7.
|
||||
|
||||
Reference in New Issue
Block a user