mirror of
https://git.vladimir.cc/vladimir/ewsdr.git
synced 2026-08-25 18:43:51 +00:00
feat(tci): этап 2 — бинарные потоки IQ/аудио, TX-аудио и запись линейного выхода
Реализованы все четыре потока §3.4 и рекордер:
• RX_AUDIO_STREAM — тап ДО громкости и мьюта (OnDemodAudioReady): скиммеру
и цифре нужен звук приёмника, а не то, что осталось после ручки;
• LINEOUT_STREAM — тап ПОСЛЕ (OnAudioReady), то есть что слышно;
• IQ_STREAM — тап сырого IQ в движке, ОДИН вызов на накопленный блок;
• TX_AUDIO_STREAM + TX_CHRONO — TRX:0,true,tci берёт модуляцию из потока
клиента (флаг TCIMicRequested впереди web в SetMOX), маркеры времени идут
из тика по часам, аудио клиента разворачивается в 48 кГц моно в тот же
ринг, что и web-микрофон;
• LINE_OUT_RECORDER_* — кольцо int16 на приёмник, WAV пишет отдельный поток.
Тапы аудио в контроллере многоадресные (AddAudioTap): слушают, ничего не
забирая, в отличие от OnAudioConsume, которым владеет web. Блоки нарезает и
раскладывает по кольцам клиентов сам DSP-поток, в сокет пишет поток клиента —
та же дисциплина, что у команд. Порядок локов везде FSliceLock → FStreamLock.
У очереди команд и кольца блоков разная политика переполнения: команду терять
нельзя, блок потока — можно (теряется самый старый).
Пересчёт частоты многоступенчатый (TCIStreams). Одноступенчатый FIR на верхнем
пресете Pluto (5760 кГц, коэффициент 120) упирался в потолок отводов и давал
завал 1.3 дБ в полосе при подавлении зеркала 16 дБ — то есть поток IQ с
мусором. Теперь коэффициент раскладывается на множители, спецификацию фильтра
каждой ступени задаёт ИТОГОВАЯ полоса, а свёртка идёт со сложением
симметричных пар: −83 дБ на любом коэффициенте, ≈10% ядра на 5.76 МГц.
Согласование частот с железом: из пресетов Pluto 576 и 960 кГц на 384 не
делятся, поэтому отдаём наибольшую ЗАКОННУЮ частоту, делящую источник нацело
(576/960 → 192 кГц). Ответ на IQ_SAMPLERATE называет достижимое, а не просьбу
клиента, и переобъявляется без запроса при смене rate и устройства.
Приёмный буфер соединения 4 → 32 КБ: блок TX-аудио это 64 байта заголовка плюс
data[16384], а кадр крупнее буфера не собирается никогда.
Настройки TCI переехали из Advanced на вкладку CAT, справа от TCP CAT Server:
это такой же канал внешнего управления трансивером.
Стенд (scratchpad, tcitest.pas): 112 проверок, все зелёные — включая сквозной
прогон через живой WDSP (синтетический IQ → блоки RX-аудио и IQ у настоящего
WS-клиента, и обратно TX-аудио клиента → блоки TX-IQ).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+194
-33
@@ -1,7 +1,7 @@
|
||||
# TCI в EWSDR — статус реализации
|
||||
|
||||
Ветка разработки: `feature/tci-protocol`.
|
||||
Дата последнего обновления: 2026-08-17.
|
||||
Дата последнего обновления: 2026-08-18.
|
||||
|
||||
Эталон протокола — «Протокол TCI, версия 2.0» Expert Electronics
|
||||
(`doc/TCI Protocol_RU.pdf`, 12 января 2024). EWSDR выступает **сервером**
|
||||
@@ -22,7 +22,8 @@ TCI-клиенты ──WebSocket──► TTCIServer ──► TTCIAdapter ─
|
||||
|---|---|
|
||||
| `TCIProtocol.pas` (~380 строк) | Чистый слой протокола: разбор `имя:арг1,арг2;`, сборка строк, экранирование `^ ~ *`, словарь видов связи, пересчёт громкости/порога в дБ. Зависит только от RTL + `RadioModes`. |
|
||||
| `TCIServer.pas` (~1250 строк) | WebSocket-сервер: accept-поток, поток на клиента, HTTP-Upgrade, разбор фреймов, рассылка, тик 20 мс. Сокеты и фреймы переиспользованы из веб-подсистемы (`WebUtils`, `WsClient`). |
|
||||
| `TCIAdapter.pas` (~2100 строк) | Мост к `TRadioController`: реализация команд, пачка инициализации, уведомления об изменениях состояния, измерители, захват параметров (§3.5). |
|
||||
| `TCIAdapter.pas` (~2600 строк) | Мост к `TRadioController`: реализация команд, пачка инициализации, уведомления об изменениях состояния, измерители, захват параметров (§3.5), подключение потоков к тапам аудио/IQ. |
|
||||
| `TCIStreams.pas` (~600 строк) | Бинарные потоки (§3.4): дециматор/интерполятор, нарезка блоков с заголовком, кольцо записи линейного выхода и писатель WAV. Зависит только от RTL + `TCIProtocol`/`TCIServer`. |
|
||||
|
||||
Принципы те же, что у CAT (см. `doc/CAT_STATUS.md`):
|
||||
|
||||
@@ -103,6 +104,15 @@ TCI-клиенты ──WebSocket──► TTCIServer ──► TTCIAdapter ─
|
||||
до `TRX`, `TUNE` и `VFO`. Своей web-странице нужен явный прокси, а не дыра
|
||||
по умолчанию.
|
||||
|
||||
6. **Блоки потоков нарезает и раскладывает DSP-поток.** Тап зовётся прямо из
|
||||
потока WDSP, поэтому в `TCIStreams` нет ни одного ожидания: блок уходит в
|
||||
кольцо клиента (микросекунды под его локом), а в сокет его пишет, как и
|
||||
команды, поток самого клиента. Порядок локов везде один: `FSliceLock` →
|
||||
`FStreamLock` (тап сначала выясняет, чей это слайс, и только потом ищет
|
||||
подписчиков) — обратный дал бы клин с потоком контроллера. Тапы навешивает
|
||||
и снимает **только поток контроллера**: снятие обязано дождаться выхода
|
||||
DSP-потока из вызова, иначе тот позвал бы метод освобождённого адаптера.
|
||||
|
||||
Разбор HTTP — построчный (`TCIHttpHeader`), а не поиском подстроки
|
||||
«`upgrade: websocket`»: заголовок с табуляцией или без пробела после
|
||||
двоеточия валиден. Close-кадр подтверждается ответным close с тем же кодом
|
||||
@@ -119,7 +129,9 @@ TCI-клиенты ──WebSocket──► TTCIServer ──► TTCIAdapter ─
|
||||
"tci": { "enabled": false, "port": 40001, "bind_addr": "127.0.0.1" }
|
||||
```
|
||||
|
||||
UI — вкладка **Advanced → TCI Server** (галка, порт, интерфейс). В протоколе
|
||||
UI — вкладка **CAT → TCI Server**, справа от «TCP CAT Server» (галка, порт,
|
||||
интерфейс): TCI — такой же канал внешнего управления трансивером, что и CAT,
|
||||
и оператор ищет его там, а не в «Advanced». В протоколе
|
||||
**нет авторизации**: открытый наружу порт означает полный доступ к трансиверу,
|
||||
поэтому умолчание слушает только петлю.
|
||||
|
||||
@@ -340,7 +352,113 @@ split, `rfMonVolume` для громкости самоконтроля и но
|
||||
ручные `PushSliceFlagState`/`LayoutFlags` в MainForm) убраны: теперь один
|
||||
путь на всех — мышь, колесо, CAT, TCI, бэнд-логика.
|
||||
|
||||
### 2.5 Телеграф (§3.2)
|
||||
### 2.5 Бинарные потоки (§3.4)
|
||||
|
||||
Реализованы все четыре типа: `IQ_STREAM`, `RX_AUDIO_STREAM`, `LINEOUT_STREAM`
|
||||
(наружу) и `TX_AUDIO_STREAM` + `TX_CHRONO` (внутрь), плюс запись линейного
|
||||
выхода в файл. Команды: `IQ_START/STOP`, `AUDIO_START/STOP`,
|
||||
`LINE_OUT_START/STOP`, `LINE_OUT_RECORDER_START/SAVE/BREAK` и параметры
|
||||
`IQ_SAMPLERATE`, `AUDIO_SAMPLERATE`, `AUDIO_STREAM_SAMPLES/CHANNELS/SAMPLE_TYPE`,
|
||||
`TX_STREAM_AUDIO_BUFFERING`.
|
||||
|
||||
**Откуда берётся звук.** Протокол различает «аудиопоток приёмника» и «поток
|
||||
линейного выхода», и это не одно и то же:
|
||||
|
||||
| Поток TCI | Точка в ewsdr | Что это значит |
|
||||
|---|---|---|
|
||||
| `RX_AUDIO_STREAM` | `OnDemodAudioReady` (тап `rakDemod`) | выход демодулятора **до** громкости и мьюта, без сайдтона — то, что нужно скиммеру и цифре |
|
||||
| `LINEOUT_STREAM` | `OnAudioReady` (тап `rakLineOut`) | ровно то, что слышно: после громкости, с сайдтоном; MUTE его глушит (движок не зовёт `OnAudio` под мьютом) |
|
||||
| `IQ_STREAM` | `TWDSPEngine.PushIQItemToDSP` / `PushPanItemToDSP` | сырой IQ приёмника, один вызов тапа на накопленный блок |
|
||||
|
||||
Тап — многоадресный и **ничего не забирает**, в отличие от `OnAudioConsume`
|
||||
(им владеет web-адаптер и им же глушит локальный звук). Иначе первый же
|
||||
TCI-клиент отобрал бы звук у динамика и у браузера.
|
||||
|
||||
Приёмник в потоках — панадаптер, как и везде (§1.3). У доп. пана в потоках
|
||||
участвует только **канал A** (первый слайс): «аудиопоток приёмника» в протоколе
|
||||
один на приёмник, отдельного потока канала B нет.
|
||||
|
||||
**Пересчёт частоты вниз — многоступенчатый.** Аудио 48 кГц → 8/12/24 и IQ
|
||||
до 48/96/192/384 кГц считает `TCIStreams`. Коэффициент раскладывается на
|
||||
множители, и каждая ступень фильтрует своё: одной ступенью большие
|
||||
коэффициенты не берутся, потому что длина FIR растёт вместе с коэффициентом и
|
||||
упирается в потолок. ★Это не теория: на верхнем пресете Pluto (5760 кГц,
|
||||
коэффициент 120 к 48 кГц) одноступенчатый дециматор давал **завал 1.3 дБ в
|
||||
полосе и подавление зеркала всего 16 дБ**, то есть поток IQ с мусором.
|
||||
|
||||
Спецификацию фильтра каждой ступени задаёт **итоговая** полоса, а не её
|
||||
собственная: ранняя ступень обязана убрать лишь те узкие зоны, которые в конце
|
||||
сложатся в полезную полосу, поэтому её переходная полоса шире в десятки раз, а
|
||||
фильтр во столько же короче. Свёртка идёт со сложением симметричных пар
|
||||
(фильтр линейнофазовый). Замеры после переделки: −83 дБ по зеркалу на **любом**
|
||||
коэффициенте, полоса ровная, цена ≈10% ядра на потоке 5.76 МГц (было 20% —
|
||||
и с мусором) и ≈1% на 384 кГц.
|
||||
|
||||
Вверх (TX-аудио клиента → 48 кГц тракта) — линейная интерполяция: её образы
|
||||
лежат за полосой TX-фильтра, а завал в полосе 0.2 дБ на 3 кГц.
|
||||
|
||||
**Ответ на `IQ_SAMPLERATE` называет достижимое, а не запрошенное.** Просьбу
|
||||
клиента храним как есть (на другом устройстве она может стать выполнимой), а в
|
||||
ответ отдаём то, что он реально получит на главном приёмнике. Подтвердить
|
||||
«384000» и слать 192 кГц значило бы соврать в единственном месте, куда клиент
|
||||
и смотрит. Из-за этого же `iq_samplerate` **переобъявляется без запроса** при
|
||||
смене частоты дискретизации и устройства (`rfSampleRate`, `rfDevice`,
|
||||
`rfConnected`) — иначе клиент, попросивший 384 кГц на openHPSDR, после
|
||||
перехода на Pluto 576 кГц молча получал бы 192. По той же логике устроен и
|
||||
`IF_LIMITS`: §4.1 прямо требует высылать его «при подключении и изменении
|
||||
частоты дискретизации устройства», и он уходит на `rfSampleRate`.
|
||||
|
||||
**Какая частота IQ достанется клиенту.** Набор протокола (48/96/192/384 кГц)
|
||||
и частоты дискретизации железа сходятся не всегда. У openHPSDR (48…384 кГц)
|
||||
сходятся все, а из пресетов Pluto (576/768/960/1536/2304/3072/3840/5760 кГц)
|
||||
на 384 не делятся 576 и 960. Поэтому отдаём **наибольшую законную** частоту,
|
||||
не выше запрошенной и делящую частоту источника нацело (`TCIPickIQRate`):
|
||||
на 576 и 960 кГц просьба «384» превращается в 192 кГц. Иначе клиент,
|
||||
попросивший 384, получал бы поток на 576/480 кГц — и не по протоколу, и вчетверо
|
||||
толще ожидаемого. Настоящая частота всегда стоит в заголовке блока; если
|
||||
законной не нашлось вовсе (чужой rate), уходит ближайшая достижимая — врать
|
||||
в заголовке мы не будем ни в каком случае. Смена rate устройства или DDC пана
|
||||
на ходу пересобирает прореживание (`SetSourceRate`).
|
||||
|
||||
**Размер блока.** `AUDIO_STREAM_SAMPLES` — это сэмплы **на канал**, а в
|
||||
`Stream.length` уходит, как велит §3.4, количество вещественных отсчётов
|
||||
(`samples × channels`). Сходится и с умолчаниями ExpertSDR3 (2048 при 48 кГц
|
||||
даёт ~43 мс), и с потолком `data[16384]`: 2048 × 2 канала × float32 = ровно
|
||||
16384 байта. Смена любого параметра потока на ходу **перезапускает** уже идущие
|
||||
потоки этого клиента: блок с новой частотой посреди старого потока клиенты
|
||||
разбирают как мусор.
|
||||
|
||||
**Передача (§3.4, §4.2).** `TRX:0,true,tci` берёт модуляцию из аудиопотока
|
||||
клиента — но только если у него запущен `AUDIO_START` (буквально по документу:
|
||||
«работает, если включен аудиопоток по TCI»). В контроллере это отдельный флаг
|
||||
`TCIMicRequested`, который `SetMOX` читает при выборе микрофона, **впереди**
|
||||
web-клиента: явная просьба сильнее умолчания «подключён браузер». Дальше тик
|
||||
сервера гонит маркеры `TX_CHRONO` по часам (плюс разовая подушка
|
||||
`TX_STREAM_AUDIO_BUFFERING` на старте передачи), а приходящие блоки
|
||||
`TX_AUDIO_STREAM` разворачиваются в 48 кГц моно и кладутся в тот же ринг
|
||||
микрофона, что и web-аудио. Аудио от клиента, который не просил `tci`,
|
||||
отбрасывается молча: отвечать ошибкой на каждый чужой блок значит захлебнуться.
|
||||
|
||||
**Запись линейного выхода.** Рекордер один на приёмник (а не на клиента):
|
||||
пишет он то, что слышно в аппарате. Кольцо на запрошенное время (потолок 300 с)
|
||||
в int16 48 кГц стерео — это ровно то, что уйдёт в WAV, и вдвое меньше памяти,
|
||||
чем float32. `SAVE` завершает запись и отдаёт кольцо отдельному потоку-писателю:
|
||||
файл бывает в десятки мегабайт, а команда пришла в потоке клиента, который в
|
||||
это время не читает свой сокет. MP3 не поддержан — кодера в проекте нет,
|
||||
и на `.mp3` уходит честный `tci_error`.
|
||||
|
||||
**Потолок кадра.** Приёмный буфер соединения (`WsClient.WS_BUF_SIZE`) поднят
|
||||
с 4 до 32 КБ: блок TX-аудио — это 64 байта заголовка плюс `data[16384]`, а
|
||||
кадр крупнее буфера не собирается никогда (BufLen упирается в потолок и разбор
|
||||
встаёт). Кадр длиннее одного блока рвёт соединение — длиннее протокол не
|
||||
определяет.
|
||||
|
||||
**Переполнение.** У очереди команд и у кольца блоков разная политика: команду
|
||||
терять нельзя (клиент выбрасывается), блок потока — можно и нужно (теряется
|
||||
самый старый, `TTCIClient.BinDropped` считает). Иначе отставший скиммер рвал бы
|
||||
себе и управление тоже.
|
||||
|
||||
### 2.6 Телеграф (§3.2)
|
||||
|
||||
`CW_MACROS`, `CW_MSG`, `CW_MACROS_STOP`, `CW_TERMINAL`.
|
||||
|
||||
@@ -368,7 +486,7 @@ split, `rfMonVolume` для громкости самоконтроля и но
|
||||
| `RX_NB_PARAM` | параметры NB в WDSP наружу не выведены |
|
||||
| `RX_BALANCE` | баланса каналов у слайса нет |
|
||||
| `DIGL_OFFSET`, `DIGU_OFFSET` | смещения цифровых мод не реализованы |
|
||||
| `RX_CHANNEL_ENABLE` | канал B главного приёмника — это VFO B, он есть всегда; создание второго слайса на пане по TCI — этап 2 |
|
||||
| `RX_CHANNEL_ENABLE` | канал B главного приёмника — это VFO B, он есть всегда; создание второго слайса на пане по TCI не делаем (см. §4) |
|
||||
|
||||
## 3.1 Ограничения, о которых честнее знать заранее
|
||||
|
||||
@@ -381,44 +499,39 @@ split, `rfMonVolume` для громкости самоконтроля и но
|
||||
| Захват параметра (§3.5) | реализован для того, что клиенты действительно перетягивают (частота, DDS, мода, фильтр, TRX/TUNE/DRIVE, split, громкости, АРУ, шумодавы, squelch, скорость CW). Эхо-параметры (RIT/XIT, BIN/ANC/…) не захватываются: на радио они не влияют |
|
||||
| Браузерные клиенты | отвергаются по `Origin` (403), см. §1.1. Web-интерфейсу ewsdr TCI не нужен — у него свой канал |
|
||||
| `TRX_COUNT` | равен `BackendCaps.MaxPans`, а не числу живых панов: протокол объявляет его один раз. Про несуществующий приёмник просто ничего не шлётся (§2.1) |
|
||||
| Потоки на передаче | RX-аудио и линейный выход на TX замолкают — движок не зовёт аудио-колбэки, пока идёт передача (кроме дуплекса с самоконтролем). То же и с IQ: RX-пакеты на TX дропаются на входе DSP. Это поведение приёмного тракта, а не потоков |
|
||||
| `MUTE` и линейный выход | глушит и поток: движок под мьютом не зовёт `OnAudio`. Аудиопоток приёмника (`AUDIO_START`) мьют не трогает — он снимается до громкости |
|
||||
| DIGL/DIGU, 2 канала | по §3.4 в цифровых модах два канала должны нести комплексный сигнал; у нас это обычное стерео с выхода WDSP. Комплексный вывод демодулятора наружу не выведен |
|
||||
| Поток канала B доп. пана | не бывает: в протоколе аудиопоток один на приёмник, и мы отдаём канал A (первый слайс) |
|
||||
| Формат IQ | всегда float32, два канала — `AUDIO_STREAM_SAMPLE_TYPE` относится к аудио (§4.3), а ExpertSDR3 IQ иначе и не шлёт |
|
||||
| `IQ_SAMPLERATE` 384 кГц на Pluto 576/960 кГц | нацело не делится, поэтому уходит 192 кГц (см. §2.5). Клиент обязан читать частоту из заголовка блока, а не считать её равной запрошенной |
|
||||
| MP3 у рекордера | не поддержан (кодера в проекте нет): `LINE_OUT_RECORDER_SAVE` с `.mp3` отвечает `tci_error` |
|
||||
|
||||
Отдельно: у `TX_SENSORS` второй аргумент — уровень микрофона; измерителя
|
||||
микрофона в EWSDR нет, шлём нижнюю границу шкалы (-60 дБм), чтобы клиент не
|
||||
рисовал случайные значения. Третий аргумент (RMS) и четвёртый (пик) отдаём
|
||||
одинаковыми — в телеметрии платы одно значение forward power.
|
||||
|
||||
У `TRX` третий аргумент (источник сигнала `tci`/`mic1`/…) игнорируется:
|
||||
аудио по TCI ещё нет, модуляция берётся из выбранного в программе входа.
|
||||
У `TRX` третий аргумент разобран только для `tci` (см. §2.5). Значения
|
||||
`mic1`/`mic2`/`micpc`/`ecoder2` называют физические входы ExpertSDR3, которых у
|
||||
нас нет: они значат «микрофон, выбранный в программе», то есть ровно то же, что
|
||||
и отсутствие аргумента.
|
||||
|
||||
---
|
||||
|
||||
## 4. Этап 2 — бинарные потоки (§3.4)
|
||||
## 4. Что осталось
|
||||
|
||||
Не реализовано ничего из потоков; команды управления ими принимаются, но
|
||||
данные не идут. Что нужно сделать:
|
||||
Бинарные потоки (§3.4) реализованы целиком — см. §2.5. Открыто:
|
||||
|
||||
1. **Заголовок блока** уже описан — `TTCIStreamHeader` в `TCIProtocol.pas`
|
||||
(16 × uint32 + сэмплы).
|
||||
2. **`RX_AUDIO_STREAM`** — нужен multicast-тап RX-аудио в контроллере.
|
||||
Сейчас есть только `OnAudioConsume` — одиночный перехват, которым владеет
|
||||
веб-адаптер (он же глушит локальный звук). Для TCI нужен именно тап
|
||||
«послушать, не забирая», по образцу `AddStateListener`.
|
||||
3. **`TX_AUDIO_STREAM` + `TX_CHRONO`** — приём бинарных фреймов от клиента
|
||||
(сейчас `TCIServer` их отбрасывает; приёмный буфер `TWsClient` — 4 КБ, под
|
||||
16 КБ блоков его придётся растить) и подача в TX-тракт наравне с
|
||||
веб-микрофоном (`PushMicSamples`).
|
||||
4. **`IQ_STREAM`** — тап сырого IQ в `TWDSPEngine.PushIQItemToDSP` (как у
|
||||
декодера маяка), с децимацией до `IQ_SAMPLERATE` (48/96/192/384 кГц).
|
||||
5. **`LINEOUT_STREAM` + `LINE_OUT_RECORDER_*`** — запись в WAV/MP3.
|
||||
|
||||
Также в очереди: `RX_CHANNEL_ENABLE` как реальное создание/удаление второго
|
||||
слайса пана и `KEYER`.
|
||||
|
||||
Приёмный буфер `TWsClient` — 4 КБ, и сейчас это жёсткий потолок: кадр крупнее
|
||||
рвёт соединение (команд такой длины у TCI нет). Под TX-аудио его придётся
|
||||
растить вместе с этапом 2.
|
||||
|
||||
---
|
||||
1. **`RX_CHANNEL_ENABLE` как реальное создание/удаление второго слайса пана.**
|
||||
Сейчас это эхо: канал B главного приёмника — VFO B, он есть всегда, а
|
||||
заводить слайс по команде клиента значит отдать ему управление раскладкой
|
||||
панорамы оператора.
|
||||
2. **`KEYER`** — своего события «ключ нажат» у контроллера нет.
|
||||
3. **TCI в демоне.** Юниты LCL-free (стенд собирает и гоняет их вместе с
|
||||
`TRadioController` без единого виджета), подключается одной строкой в
|
||||
`ewsdrd.lpr`, как web.
|
||||
4. **Проверка на железе и с настоящим клиентом** — главное, см. конец §5.
|
||||
|
||||
## 5. Проверено
|
||||
|
||||
@@ -478,6 +591,50 @@ split, `rfMonVolume` для громкости самоконтроля и но
|
||||
движков и сети валится с AV — клиент получает `tci_error`, соединение живо) и
|
||||
неразрывность пачки инициализации под крутящейся ручкой.
|
||||
|
||||
### Стенд этапа 2 (бинарные потоки) — 112 проверок, все зелёные
|
||||
|
||||
Отдельная программа (`tcitest.pas` в scratchpad, собирается тем же fpc без
|
||||
внешних библиотек) проверяет потоки на четырёх уровнях:
|
||||
|
||||
- **Формат и математика:** коэффициенты прореживания (в том числе «просят выше,
|
||||
чем есть» и «нацело не делится»), умолчания размера блока, поля заголовка,
|
||||
сквозная упаковка/распаковка всех четырёх форматов сэмплов и клип на
|
||||
перегрузе, разбор пути с `|` вместо `:`.
|
||||
- **Пересчёт частоты:** единичное усиление дециматора на постоянке, тон 1 кГц
|
||||
проходит, тон 20 кГц при 48→12 кГц давится больше чем на 40 дБ (то самое
|
||||
зеркало, ради которого и стоит фильтр), прозрачность при коэффициенте 1,
|
||||
счёт отсчётов у интерполятора и отсутствие выбросов. Отдельно — каскад на
|
||||
всех интересных коэффициентах (192→48, 384→48 у openHPSDR; 576→48, 1536→48,
|
||||
5760→48 и 5760→384 у Pluto): полоса ровная, зеркало давится больше чем на
|
||||
60 дБ. И выбор законной частоты IQ: 576 и 960 кГц на просьбу «384» отдают
|
||||
192 кГц, 768 кГц отдаёт 384 кГц, чужой rate возвращает просьбу как есть.
|
||||
- **Блок до сокета** (пара сокетов вместо сети): четыре блока RX-аудио 12 кГц
|
||||
стерео float32 с верным заголовком и длиной; IQ 384→48 кГц уходит только
|
||||
целым блоком (полблока не отправляется); моно int16; пересчёт после смены
|
||||
rate источника; переполнение кольца теряет старые блоки, но клиент жив.
|
||||
- **Рекордер и WAV:** кольцо ограничено запрошенным временем, после заворота
|
||||
первым идёт самый старый отсчёт, `Take` опустошает, файл получает верные
|
||||
RIFF/fmt/data и длину.
|
||||
- **Команды на живом сервере** (настоящий `TRadioController`, WS-клиент на
|
||||
сыром сокете): отказ на несуществующий приёмник и на нечисловой аргумент,
|
||||
отказ на старт потока с незапущенного пана, подтверждение и отбраковка
|
||||
параметров, `SAVE` без записи и `SAVE` в `.mp3` отвечают ошибкой,
|
||||
`TRX:0,true,tci` без аудиопотока модуляцию не берёт, а с потоком берёт и
|
||||
снимает её по `TRX:0,false` и по уходу клиента; чужой бинарный блок не рвёт
|
||||
соединение; без передачи маркеров `TX_CHRONO` нет. Отдельно — согласование
|
||||
частот: на источнике 192 кГц просьба «384» подтверждается как 192, смена
|
||||
rate устройства сама переобъявляет и `iq_samplerate`, и `if_limits`, а на
|
||||
576 кГц (Pluto) та же просьба даёт законные 192 кГц.
|
||||
- **Сквозной прогон через живой WDSP:** синтетический 24-битный IQ подаётся
|
||||
в движок, а клиент по WebSocket получает блоки RX-аудио 12 кГц и IQ 48 кГц
|
||||
с верными заголовками; после `AUDIO_STOP`/`IQ_STOP` блоки прекращаются.
|
||||
Передача: `TRX:0,true,tci` поднимает маркеры `TX_CHRONO`, а присланное
|
||||
клиентом аудио 12 кГц доходит до конца тракта (появляются блоки TX-IQ).
|
||||
|
||||
Чего стенд этапа 2 не проверяет: доп. паны (для них нужен живой DDC),
|
||||
одновременную работу нескольких клиентов на одном потоке и длительный прогон
|
||||
(дрейф пейсинга TX_CHRONO виден только на минутах).
|
||||
|
||||
Чего стенд не проверяет: поведение пана без слайсов и рассылку каналов при
|
||||
создании/удалении слайса — для них нужен живой DSP-движок, которого на стенде
|
||||
нет. Остаётся и известное окно: показания измерителей читают `FDSPEngine` из
|
||||
@@ -494,8 +651,12 @@ send просто возвращает EPIPE, и клиент выбрасыва
|
||||
TCI в его граф пока не заведён — юниты LCL-free, подключается одной строкой в
|
||||
`ewsdrd.lpr`, как web).
|
||||
|
||||
★Пробная сборка стенда: `-Mobjfpc` обязателен. С `-Mdelphi` в командной строке
|
||||
вложенные комментарии выключаются, и `{$MODE Delphi}` внутри шапки `WebUtils.pas`
|
||||
закрывает комментарий раньше времени — компиляция падает на «illegal character».
|
||||
|
||||
**На реальном железе и с реальным клиентом (Log4OM/N1MM/WSJT-X/CW Skimmer) не
|
||||
проверялось.**
|
||||
проверялось — ни команды, ни потоки.**
|
||||
|
||||
Ответ на команду-установку клиент получает дважды: прямым ответом и рассылкой
|
||||
из `OnState`. Это осознанно — дубли идемпотентны, а рассылка нужна для тех
|
||||
|
||||
Reference in New Issue
Block a user