chore: refine project documentation

This commit is contained in:
2026-08-25 22:44:36 +03:00
parent 8237f03930
commit 98d90e1e5a
45 changed files with 457 additions and 516 deletions
+61 -62
View File
@@ -19,10 +19,10 @@
unit CATEngine;
{
CATEngine.pas — CAT command engine (Kenwood + Thetis ZZ* extensions).
CATEngine.pas — CAT command engine (Kenwood + extended ZZ* commands).
Совместимость с Thetis/PowerSDR: все команды из CATParser.cs + CATCommands.cs
реализованы. Команды, для которых в ewsdr ещё нет функционала, возвращают
Движок поддерживает базовые команды Kenwood и расширения ZZ*. Команды, для
которых в EWSDR ещё нет функционала, возвращают
заглушку и сопровождены комментарием "STUB: <причина>".
Формат команд:
@@ -160,7 +160,7 @@ type
CWTextBusy: TCATGetBoolFunc;
// Телеграф, продолжение: тон приёма/сайдтона (ZZCL), break-in прошивки
// (ZZCB) и его hang-time (ZZCD), сайдтон активного источника манипуляции
// (ZZCM у Thetis это «monitor DISABLE», инверсия делается в движке).
// ZZCM означает «monitor DISABLE», инверсия делается в движке.
GetCWPitch: TCATGetIntFunc; // Гц
SetCWPitch: TCATSetIntProc;
GetCWBreakIn: TCATGetBoolFunc;
@@ -500,7 +500,7 @@ begin if B then Result := '1' else Result := '0'; end;
function IsParamless(const Pfx: string): Boolean;
// Кенвудовские команды, у которых и SET, и GET идут с пустым суффиксом
// (nsetparms=0 / ngetparms=0 в CATStructs.xml). Любой хвост у них — ошибка
// Команды не принимают аргументов; любой хвост у них — ошибка
// формата, а не значение.
const
NoArg: array[0..9] of string = (
@@ -753,7 +753,7 @@ end;
function TCATEngine.NumField(const s: string; Width, Lo, Hi: Integer;
const Getter: TCATGetIntFunc; const Setter: TCATSetIntProc): string;
// Числовое поле фиксированной ширины: SET клампует в [Lo..Hi] (как Thetis —
// Числовое поле фиксированной ширины: SET клампует в [Lo..Hi],
// выход за границы не ошибка, а подрезка), GET дополняет нулями слева.
var v: Integer;
begin
@@ -769,7 +769,7 @@ begin
end;
function TCATEngine.StepIdxToHz(Idx: Integer): Double;
// Шаг настройки по индексу (0..14) — зеркало Thetis Step2Freq (в Гц).
// Шаг настройки по протокольному индексу 0..14, результат в Гц.
const Steps: array[0..14] of Double = (
1, 10, 25, 50, 100, 250, 500, 1000, 5000, 9000,
10000, 100000, 250000, 500000, 1000000);
@@ -823,8 +823,8 @@ begin
Result := ParseZZ(ext, sfx);
end else begin
sfx := Copy(s, 3, tpos - 3);
// Команды без параметров: эталон отбраковывает лишний суффикс в парсере,
// по таблице ширин, ДО вызова команды. Таблицы у нас нет — держим список
// Команды без параметров должны отбраковывать лишний суффикс в парсере
// ДО вызова команды. Таблицы ширин у нас нет — держим список
// здесь, иначе «TXfoo;» дошёл бы до CmdTX и поднял передачу.
if (sfx <> '') and IsParamless(pfx) then Exit(CAT_ERROR);
if pfx = 'AG' then Result := CmdAG(sfx)
@@ -933,7 +933,7 @@ begin
end;
function TCATEngine.CmdCT(const s: string): string;
// CT: включение CTCSS — обёртка над ZZTA, как и в эталоне.
// CT: включение CTCSS — обёртка над ZZTA.
begin
Result := ZZTA(s);
end;
@@ -994,7 +994,7 @@ end;
function TCATEngine.CmdFW(const s: string): string;
// FW: filter width (4 digits) — maps to filter index
// Thetis encodes filter by index into 4-digit string 0000-0009
// Filter index is encoded as a 4-digit string from 0000 to 0009.
var idx: Integer;
begin
if Length(s) = 4 then begin
@@ -1028,7 +1028,7 @@ begin
end;
function TCATEngine.CmdIF: string;
// IF: составной статус — 35 символов ответа (как у Thetis; «37 байт» из
// IF: составной статус — 35 символов ответа («37 байт» из
// прежнего комментария — это вся посылка вместе с префиксом и ';').
var
freq, step, incr, rit, xit, tx, mode, split: string;
@@ -1108,7 +1108,7 @@ end;
function TCATEngine.CmdMG(const s: string): string;
// MG: усиление микрофона по-кенвудовски (000..100). Шкала 100:70 к дБ — как в
// Thetis (n/1.43), дальше кламп под диапазон вкладки Transmit. Отрицательные дБ
// n/1.43, дальше кламп под диапазон вкладки Transmit. Отрицательные дБ
// в кенвудовскую шкалу не ложатся: на чтении подрезаем к нулю.
var v: Integer;
begin
@@ -1159,22 +1159,21 @@ begin
end;
function TCATEngine.CmdOF(const s: string): string;
// OF: сдвиг репитера — обёртка над ZZOT, как и в эталоне.
// OF: сдвиг репитера — обёртка над ZZOT.
begin
Result := ZZOT(s);
end;
function TCATEngine.CmdOS(const s: string): string;
// OS: направление сдвига репитера — обёртка над ZZOS, как и в эталоне.
// OS: направление сдвига репитера — обёртка над ZZOS.
begin
Result := ZZOS(s);
end;
function TCATEngine.ZZOS(const s: string): string;
// ZZOS: направление сдвига репитера. Снаружи (и у Thetis) 0=симплекс, 1=вверх,
// ZZOS: направление сдвига репитера. В протоколе 0=симплекс, 1=вверх,
// 2=вниз; внутри у нас RPT_NONE/MINUS/PLUS — отсюда трансляция.
// ★Мусор эталон молча трактует как симплекс (default в String2OffsetDirection),
// и мы повторяем это намеренно: клиенты рассчитывают на такое поведение.
// ★Некорректное значение трактуется как симплекс для совместимости клиентов.
var dir: Integer;
begin
if Length(s) = 1 then begin
@@ -1196,9 +1195,9 @@ begin
end;
function TCATEngine.ZZOT(const s: string): string;
// ZZOT: величина сдвига репитера, 9 цифр. Эталон читает поле как МГц с шестью
// знаками после запятой (вставляет точку после третьего разряда) — численно это
// ровно те же герцы, что и у нас. Знак не передаётся, направление в ZZOS.
// ZZOT: величина сдвига репитера, 9 цифр. Поле кодирует МГц с шестью знаками
// после запятой, что численно соответствует целому значению в герцах. Знак не
// передаётся, направление задаётся в ZZOS.
var v: Int64;
begin
if Length(s) = 9 then begin
@@ -1228,7 +1227,7 @@ begin
end;
function TCATEngine.CmdPR(const s: string): string;
// PR: речевой компрессор вкл/выкл. У Thetis команда только читает (всегда '0');
// PR: речевой компрессор вкл/выкл. Базовая команда доступна только для чтения;
// у нас компрессор настоящий, поэтому SET тоже работает — расширение вверх,
// кенвудовские клиенты от него не ломаются.
begin
@@ -1311,7 +1310,7 @@ end;
function TCATEngine.CmdSM(const s: string): string;
// SM: S-метр. Запрос — один символ-селектор приёмника, ответ — ПЯТЬ цифр
// (шкала Kenwood 00000..00030: до 00015 это S0..S9, дальше +1 за каждый дБ).
// Селектор Thetis шлёт как '0' или '2', TS-2000 — как '0'/'1'; принимаем все:
// Совместимые клиенты присылают селектор '0', '1' или '2'; принимаем все:
// второго приёмника нет, отвечаем одним и тем же.
var sm: Integer;
begin
@@ -1401,7 +1400,7 @@ begin
else if ext = 'BY' then ans := '' // STUB: выключение консоли — по CAT не гасимся
else if ext = 'CB' then ans := ZZCB(s)
else if ext = 'CD' then ans := ZZCD(s)
else if ext = 'CF' then ans := '' // STUB: показывать частоту телеграфа (UI Thetis)
else if ext = 'CF' then ans := '' // STUB: показывать частоту телеграфа в UI
else if ext = 'CI' then ans := ZZCI(s)
else if ext = 'CL' then ans := ZZCL(s)
else if ext = 'CM' then ans := ZZCM(s)
@@ -1443,10 +1442,10 @@ begin
else if ext = 'FR' then ans := '' // STUB: верхняя кромка фильтра RX2 — RX2 нет
else if ext = 'FS' then ans := '' // STUB: нижняя кромка фильтра RX2 — RX2 нет
else if ext = 'FT' then ans := ZZFT(s)
else if ext = 'FV' then ans := '' // STUB: чтение FlexWire (1 байт) — шины нет
else if ext = 'FW' then ans := '' // STUB: чтение FlexWire (2 байта) — шины нет
else if ext = 'FX' then ans := '' // STUB: запись FlexWire (1 байт) — шины нет
else if ext = 'FY' then ans := '' // STUB: запись FlexWire (2 байта) — шины нет
else if ext = 'FV' then ans := '' // STUB: чтение вендорской шины (1 байт)
else if ext = 'FW' then ans := '' // STUB: чтение вендорской шины (2 байта)
else if ext = 'FX' then ans := '' // STUB: запись вендорской шины (1 байт)
else if ext = 'FY' then ans := '' // STUB: запись вендорской шины (2 байта)
else if ext = 'GA' then ans := ZZGA(s)
else if ext = 'GE' then ans := '' // STUB: кнопка noise gate — такого блока нет
else if ext = 'GL' then ans := '' // STUB: уровень noise gate — такого блока нет
@@ -1466,16 +1465,16 @@ begin
else if ext = 'IS' then ans := ZZIS(s)
else if ext = 'IT' then ans := '' // STUB: сдвиг IF — полосу двигаем кромками (ZZFL/ZZFH)
else if ext = 'IU' then ans := '' // STUB: сброс сдвига фильтра — см. ZZIT
else if ext = 'JO' then ans := '' // STUB: нет такой команды у Thetis
else if ext = 'JP' then ans := '' // STUB: служебная JSON-команда Thetis
else if ext = 'JQ' then ans := '' // STUB: служебная JSON-команда Thetis
else if ext = 'JR' then ans := '' // STUB: служебная JSON-команда Thetis
else if ext = 'JS' then ans := '' // STUB: служебная JSON-команда Thetis
else if ext = 'JO' then ans := '' // STUB: команда не описана
else if ext = 'JP' then ans := '' // STUB: служебная JSON-команда
else if ext = 'JQ' then ans := '' // STUB: служебная JSON-команда
else if ext = 'JR' then ans := '' // STUB: служебная JSON-команда
else if ext = 'JS' then ans := '' // STUB: служебная JSON-команда
else if ext = 'KM' then ans := ZZKM(s)
else if ext = 'KO' then ans := '' // STUB: без описания у Thetis
else if ext = 'KO' then ans := '' // STUB: команда не описана
else if ext = 'KS' then ans := ZZKS(s)
else if ext = 'KY' then ans := ZZKY(s)
else if ext = 'LA' then ans := '' // STUB: уровень RX0 — микшер Thetis
else if ext = 'LA' then ans := '' // STUB: уровень RX0 внешнего микшера
else if ext = 'LB' then ans := '' // STUB: стереобаланс главного приёмника — микшера нет
else if ext = 'LC' then ans := '' // STUB: уровень sub-RX — sub-RX нет
else if ext = 'LD' then ans := '' // STUB: стереобаланс sub-RX — sub-RX нет
@@ -1487,7 +1486,7 @@ begin
else if ext = 'MA' then ans := ZZMA(s)
else if ext = 'MB' then ans := ZZMB(s)
else if ext = 'MD' then ans := ZZMD(s)
else if ext = 'ME' then ans := '' // STUB: без описания у Thetis
else if ext = 'ME' then ans := '' // STUB: команда не описана
else if ext = 'MF' then ans := '' // STUB: текст на LCD мультифункционального энкодера Andromeda — не реализовано
else if ext = 'MG' then ans := ZZMG(s)
else if ext = 'ML' then ans := ZZML
@@ -1528,7 +1527,7 @@ begin
else if ext = 'OF' then ans := '' // STUB: реле TX на разъёме RCA — управления реле нет
else if ext = 'OG' then ans := '' // STUB: разрешение задержек реле TX — см. ZZOF
else if ext = 'OH' then ans := '' // STUB: величины задержек реле TX — см. ZZOF
else if ext = 'OJ' then ans := '' // STUB: без описания у Thetis
else if ext = 'OJ' then ans := '' // STUB: команда не описана
else if ext = 'OL' then ans := '' // STUB: click-tune офсет для DIGL — офсетов режима нет
else if ext = 'OS' then ans := ZZOS(s)
else if ext = 'OT' then ans := ZZOT(s)
@@ -1548,7 +1547,7 @@ begin
else if ext = 'PS' then ans := ZZPS(s)
else if ext = 'PY' then ans := '' // STUB: зум дисплея — не проброшено
else if ext = 'PZ' then ans := '' // STUB: кнопки зума дисплея — не проброшено
// ZZQK: у Thetis break-in делится на semi и QSK; у нас break-in один
// ZZQK различает semi и QSK; у нас break-in один
// (прошивочный), а разница semi/QSK выражается hang-time'ом — ZZCD.
else if ext = 'QK' then ans := '' // STUB: нет отдельного переключателя semi/QSK
else if ext = 'QM' then ans := ZZQM
@@ -1580,7 +1579,7 @@ begin
else if ext = 'SP' then ans := ZZSP(s)
else if ext = 'SQ' then ans := ZZSQ(s)
else if ext = 'SR' then ans := ZZSR(s)
else if ext = 'SS' then ans := '' // STUB: без описания у Thetis
else if ext = 'SS' then ans := '' // STUB: команда не описана
else if ext = 'ST' then ans := ZZST(s)
else if ext = 'SU' then begin ZZSU; ans := ''; end
else if ext = 'SV' then ans := '' // STUB: шумоподавитель RX2 — RX2 нет
@@ -1590,14 +1589,14 @@ begin
else if ext = 'SZ' then ans := '' // STUB: подгон VFO под шаг настройки — см. ZZSY
else if ext = 'TA' then ans := ZZTA(s)
else if ext = 'TB' then ans := ZZTB(s)
else if ext = 'TF' then ans := '' // STUB: показывать полосу TX на спектре (UI Thetis)
else if ext = 'TF' then ans := '' // STUB: показывать полосу TX на спектре
else if ext = 'TH' then ans := ZZTH(s)
else if ext = 'TI' then ans := '' // STUB: запрет выхода мощности (внешние антенны/тюнер)
else if ext = 'TL' then ans := ZZTL(s)
else if ext = 'TM' then ans := ZZTM(s)
else if ext = 'TO' then ans := ZZTO(s)
else if ext = 'TP' then ans := ZZTP(s)
else if ext = 'TS' then ans := '' // STUB: датчик температуры Flex 5000
else if ext = 'TS' then ans := '' // STUB: неподдерживаемый датчик температуры
else if ext = 'TU' then ans := ZZTU(s)
else if ext = 'TV' then ans := '' // STUB: TX-частота при split с RX2
else if ext = 'TX' then ans := ZZTX(s)
@@ -1629,7 +1628,7 @@ begin
else if ext = 'VT' then ans := '' // STUB: выходной кабель VAC2 — VAC нет
else if ext = 'VU' then ans := '' // STUB: частота дискретизации VAC2 — VAC нет
else if ext = 'VV' then ans := '' // STUB: галка VAC2 Stereo — VAC нет
else if ext = 'VW' then ans := '' // STUB: без описания у Thetis
else if ext = 'VW' then ans := '' // STUB: команда не описана
else if ext = 'VX' then ans := '' // STUB: усиление передачи в VAC2 — VAC нет
else if ext = 'VY' then ans := '' // STUB: размер буфера VAC1 — VAC нет
else if ext = 'VZ' then ans := '' // STUB: размер буфера VAC1 (второй) — VAC нет
@@ -1654,7 +1653,7 @@ begin
else if ext = 'WT' then ans := '' // STUB: уровень микрофона в микшере F1500 — железо другого вендора
else if ext = 'WU' then ans := '' // STUB: уровень входа FireWire в микшере F1500 — см. ZZWT
else if ext = 'WV' then ans := '' // STUB: уровень наушников в микшере F1500 — см. ZZWT
else if ext = 'WW' then ans := '' // STUB: уровень выхода FlexWire в микшере F1500 — см. ZZWT
else if ext = 'WW' then ans := '' // STUB: уровень выхода вендорской шины — см. ZZWT
else if ext = 'XA' then ans := ZZXA(s)
else if ext = 'XC' then ans := ZZXC
else if ext = 'XD' then ans := '' // STUB: убавить XIT — расстройки передачи нет
@@ -1846,7 +1845,7 @@ begin
end;
function TCATEngine.ZZBS(const s: string): string;
// ZZBS: выбор диапазона. ⚠ Отклонение от Thetis: там поле трёхсимвольное и
// ZZBS: выбор диапазона. В EWSDR поле двузначное, а базовый формат использует
// несёт КОД диапазона ("160"/"040"/"WWV"), у нас — двузначный ИНДЕКС в списке
// диапазонов EWSDR. Менять поздно: на этом формате уже сидят клиенты.
// ★Нечисловое поле — ошибка формата: StrToIntDef переключал бы на диапазон 0,
@@ -1877,10 +1876,10 @@ begin
end;
function TCATEngine.ZZCD(const s: string): string;
// ZZCD: задержка отпускания break-in (hang time), мс. Эталон принимает
// 150..5000, у нас потолок 2000 — это НАШ предел, а не описка: столько же
// ZZCD: задержка отпускания break-in (hang time), мс. В EWSDR потолок 2000 —
// это намеренный предел: столько же
// стоит в настройках телеграфа и уходит в поле HangDelay пакета DUC Specific.
// Значение выше подрезается (как и всё прочее у Thetis), формат — те же 4 цифры.
// Значение выше подрезается по общему правилу, формат — те же 4 цифры.
begin
Result := NumField(s, 4, 0, CW_HANG_MAX, FCtx.GetCWHangTime, FCtx.SetCWHangTime);
end;
@@ -1948,7 +1947,7 @@ begin
end;
function TCATEngine.ZZEB(const s: string): string;
// ZZEB: значения TX-эквалайзера одной строкой (формат Thetis).
// ZZEB: значения TX-эквалайзера одной строкой в протокольном формате.
// nnn + (nnn){nb+1}, добито нулями до 36 символов;
// nb — число полос, следом преамп и полосы, отрицательные как '-nn'.
var
@@ -2146,7 +2145,7 @@ end;
function TCATEngine.ZZKM(const s: string): string;
// ZZKM: запустить CW-макрос номер n (1..9) — у нас это ячейка памяти сообщений
// (окно CW Messages, F1..F8). Write-only, как в Thetis.
// (окно CW Messages, F1..F8). Команда доступна только для записи.
// Раньше здесь сидел режим кейера — своей команды у него в протоколе нет,
// иамбик вкл/выкл доступен через ZZCI.
var v: Integer;
@@ -2198,7 +2197,7 @@ begin
end;
function TCATEngine.ZZMG(const s: string): string;
// ZZMG: усиление микрофона в дБ. Формат Thetis: SET — три цифры либо знак+две
// ZZMG: усиление микрофона в дБ. SET — три цифры либо знак+две
// ('+10'/'-05'), GET всегда отдаёт знак+две. Значение уезжает в активный
// TX-профиль (MicGainDB), поэтому подрезаем под диапазон вкладки Transmit.
var v: Integer;
@@ -2206,7 +2205,7 @@ begin
if Length(s) = 0 then
Exit(SignedPad(EnsureRange(SafeGetMicGain, MIC_GAIN_MIN, MIC_GAIN_MAX), 2));
// Три символа (`025`, `+10`, `-05`) либо четыре со знаком (`-050`) — ровно
// как у Thetis (nSet и nSet+1). Более короткие поля — ошибка формата, а не
// Допустимы две протокольные длины поля. Более короткие поля — ошибка формата, а не
// «маленькое значение»: молча крутить усиление по ним нельзя.
if (Length(s) < 3) or (Length(s) > 4) then Exit(CAT_ERROR);
if not TryStrToInt(s, v) then Exit(CAT_ERROR);
@@ -2220,7 +2219,7 @@ function TCATEngine.ZZML: string;
begin Result := 'LSB USB DSB CWL CWU FM AM SAM DIGUDIGLWFM DMR FMRAW'; end;
function TCATEngine.ZZMN(const s: string): string;
// ZZMN: имя режима по номеру. ⚠ Отклонение от Thetis: там ZZMN отдаёт пресеты
// ZZMN: имя режима по номеру. В базовом формате ZZMN отдаёт пресеты
// DSP-фильтров (180 символов). Имён режимов в протоколе нет вовсе, а список
// режимов (ZZML) на эту команду опирается — оставляем как расширение EWSDR.
// Индекс режима двузначный (MODE_MIN..MODE_MAX), но односимвольный запрос
@@ -2441,12 +2440,12 @@ begin
end;
function TCATEngine.ZZRV: string;
// ZZRV: напряжение питания PA, формат Thetis «NN.N» (4 символа).
// ZZRV: напряжение питания PA, формат «NN.N» (4 символа).
// (Версия ПО — это ZZVN; раньше ZZRV отдавал её.)
var v: Double;
begin
if Assigned(FCtx.GetSupplyVolts) then v := FCtx.GetSupplyVolts else v := -1;
if v < 0 then v := 0; // телеметрии нет — отвечаем нулём, как Thetis
if v < 0 then v := 0; // телеметрии нет — отвечаем нулём
if v > 99.9 then v := 99.9;
Result := Format('%04.1f', [v]);
end;
@@ -2459,7 +2458,7 @@ begin
end;
function TCATEngine.ZZSA: string;
// ZZSA: шаг настройки вниз. У Thetis шагает именно VFO A, у нас — активный VFO
// ZZSA: шаг настройки вниз. В EWSDR шагает активный VFO,
// (отдельного «шага VFO A» в контроллере нет).
begin
if Assigned(FCtx.DoTuneDown) then FCtx.DoTuneDown;
@@ -2652,8 +2651,8 @@ begin
Result := '';
end;
// ZZWA..ZZWS: микшер Flex 5000 / FLEX-1500 (уровни и галки входов/выходов) —
// STUB: железо другого вендора, аналога нет. Водопад тут ни при чём: его
// ZZWA..ZZWS: неподдерживаемые команды внешнего микшера — STUB.
// Водопад тут ни при чём: его
// уровни живут в ZZDN/ZZDO.
function TCATEngine.ZZWA(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
@@ -2714,7 +2713,7 @@ function TCATEngine.ZZYR(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
// ZZZA: трип-состояние усилителя Ganymede; ZZZB, ZZZQ, ZZZR: внутренние
// служебные Thetis; ZZZM/ZZZV: строки модели и версии железа; ZZZT: зум на
// служебные команды; ZZZM/ZZZV: строки модели и версии железа; ZZZT: зум на
// диапазон; ZZZW: обмен «колёс» VFO в midi2cat; ZZZO: быстрый split.
// Всё — STUB: либо чужое железо, либо в CAT не проброшено.
function TCATEngine.ZZZA(const s: string): string;
@@ -2732,7 +2731,7 @@ begin
end;
function TCATEngine.ZZZE(const s: string): string;
// ZZZE: Andromeda — поворот энкодера. Кодировка Thetis: верхние 2 цифры номер
// ZZZE: Andromeda — поворот энкодера. Верхние 2 цифры кодируют номер
// энкодера (01..20 = +шаг, 51..70 = −шаг), нижняя цифра — величина шага.
var raw, enc, step: Integer;
begin
@@ -2797,7 +2796,7 @@ begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZW(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZZZ: string;
// ZZZZ: без описания у Thetis — STUB.
// ZZZZ: команда не описана — STUB.
begin Result := ''; end;
function TCATEngine.ZZZO(const s: string): string;
begin if Length(s) > 0 then Result := '' else Result := '0'; end;
@@ -2806,7 +2805,7 @@ begin if Length(s) > 0 then Result := '' else Result := '0'; end;
function TCATEngine.ZZCI(const s: string): string;
// ZZCI: иамбический манипулятор вкл/выкл. У нас режим кейера трёхпозиционный
// (прямой ключ / iambic A / iambic B) — включение выбирает B, как в Thetis по
// (прямой ключ / iambic A / iambic B) — включение выбирает B по
// умолчанию, а если иамбик уже стоит, ранее выбранная буква сохраняется.
const KEYER_STRAIGHT = 0; KEYER_IAMBIC_B = 2;
begin
@@ -2827,7 +2826,7 @@ begin
end;
function TCATEngine.ZZCM(const s: string): string;
// ZZCM: у Thetis это «CW Monitor DISABLE» значение инвертировано к сайдтону.
// ZZCM означает «CW Monitor DISABLE», значение инвертировано к сайдтону.
begin
if Length(s) = 0 then Exit(B2C(not SafeGetBool(FCtx.GetCWMonitor)));
if (s <> '0') and (s <> '1') then Exit(CAT_ERROR);