В первой статье мы рассказали, как впятером делали голосовую связь и несколько недель обвиняли Opus в провалах, которые на самом деле создавал наш логгер.
После публикации обсуждение довольно быстро вышло за пределы кодека. Спасибо всем, кто прочитал статью и поделился ценными мыслями. Благодаря вам мы внимательнее посмотрели на Windows-клиент и задумались, как отделить работающий звуковой тракт от интерфейса и попробовать более современную оболочку.
Самый простой ответ звучал бы так: «Win32 работает — не трогайте». И это не шутка. Через Win32 у нас уже работали глобальный push-to-talk, трей, прозрачный click-through оверлей поверх игры, выбор устройств и жизненный цикл окон. Голосовой тракт на C++ использовал WASAPI, Opus, RNNoise и SpeexDSP. Переписывать всё ради скруглённых кнопок было бы плохим обменом.
И эти мысли попали в больное место. К этому моменту один только SetupWindow.cpp вырос более чем до 6000 строк. В нём жили создание контролов, раскладка, GDI/GDI+ рисование, анимация человечка, комнаты, аватары, ввод PIN, настройки микрофона и обработка сообщений Windows. Каждая новая панель означала ещё набор координат, состояний, перерисовок и ручных проверок DPI.
Так появилась не идея «переписать БОЛТУН на модный фреймворк», а более узкий вопрос:
На каком стеке развивать следующий Desktop-интерфейс, если real-time голос, PTT, сеть и оверлей должны остаться в уже работающем нативном ядре?
Мы не стали переделывать стабильный клиент на глазах у пользователей. Сначала развернули отдельную тестовую ветку БОЛТУНа. На ней изучили три варианта для нового интерфейсного стека — Electron, WinUI 3 и Tauri 2, — а затем собрали вертикальный прототип на Tauri. Эта статья — промежуточный отчёт: выбор для эксперимента уже сделан, но решение для рабочего клиента ещё предстоит подтвердить интеграцией и замерами.
Почему Win32 вообще был разумным выбором
Первая версия начиналась как небольшая говорилка для пяти человек, а не как продукт с дизайн-системой. Win32 давал всё, что требовалось рядом с игрой:
обычный нативный EXE без отдельной GUI-среды выполнения в комплекте;
прямой доступ к циклу сообщений, устройствам и системным событиям;
глобальные клавиши через
RegisterHotKeyиGetAsyncKeyState;прозрачный оверлей с
WS_EX_LAYERED,WS_EX_TOPMOST,WS_EX_NOACTIVATEиWS_EX_TRANSPARENT;трей и несколько небольших служебных окон;
отсутствие ещё одного IPC-слоя между интерфейсом и кодом на C++.
Для первой версии это было быстрее и предсказуемее любого большого архитектурного проекта.
Проблема появилась не потому, что Win32 внезапно испортился. Изменился сам БОЛТУН. К лобби добавились полноценные комнаты, персональная громкость, обработка звука, уведомления, обновления, радио, интерактивный человечек и новый визуальный язык. Интерфейс перестал быть тонкой оболочкой над пятью кнопками.
Win32 по-прежнему прекрасно решал системные задачи, но всё дороже решал задачу «нарисовать и поддерживать современное приложение».
Сначала развернули отдельную тестовую ветку
Тестовая ветка — не новое название продукта и не отдельный голосовой сервис. Это тот же БОЛТУН, но с собственным закрытым контуром для изменений, которые пока рано отдавать всем.
Мы развернули для неё отдельный сайт, отдельный процесс Go-координатора, собственный Hub с комнатами и отдельный каталог данных. Тестовые Windows- и Android-сборки жёстко направлены на тестовый WebSocket. Их комнаты не смешиваются с комнатами стабильной версии. Страница закрыта паролем и помечена noindex, поэтому эксперимент не выглядит как публичный релиз.
При этом контуры пока живут на одном сервере. Изоляция защищает данные, комнаты и версии, но тяжёлый тест всё ещё способен конкурировать со стабильным процессом за CPU и сеть. Поэтому тестовая ветка — страховка от функциональных ошибок, а не лицензия бездумно нагружать машину.
Именно туда мы сначала добавили новый интерфейс, двухэтапную автокалибровку и экспериментальную Desktop-оболочку. Стабильный БОЛТУН продолжил работать на прежнем коде.
Три кандидата: Electron, WinUI 3 и Tauri 2
Мы не начинали сравнение с вопроса «какой фреймворк моднее». У голосового клиента уже были ограничения, которые нельзя было проигнорировать:
существующее ядро на C++ со звуком, сетью и DSP нельзя переписывать вместе с интерфейсом;
глобальный PTT, трей и игровой оверлей должны сохранить нативное поведение;
PCM-кадры не должны ходить через UI-IPC;
хотелось повторно использовать TypeScript, CSS, SVG и часть логики веб-клиента;
приложение постоянно висит рядом с игрой, поэтому цену среды выполнения нужно не угадывать, а измерять;
переход должен быть поэтапным: сначала тестовая ветка и узкий мост к ядру, затем решение по рабочему клиенту.
С этими критериями у каждого кандидата обнаружился свой сильный сценарий.
Первое сравнение фреймворков мы сделали с помощью ИИ. Описали ему устройство БОЛТУНа, существующее ядро на C++ и наши требования, а он помог быстро разложить Electron, WinUI 3 и Tauri 2 по плюсам, минусам и возможным проблемам. После этого мы перепроверили важные пункты по официальной документации и перешли к прототипу. ИИ заметно ускорил исследование, но окончательный выбор мы делали уже под свой проект и свои ограничения.
Electron: наиболее прямой путь для веб-команды
Electron выглядел самым знакомым вариантом. Интерфейс можно собирать привычными веб-инструментами, поведение Chromium одинаково на поддерживаемых системах, а вокруг фреймворка есть зрелая экосистема. Для быстрого переноса веб-интерфейса это серьёзные плюсы.
Но Electron — это не просто окно с HTML. В его официальной модели процессов главный процесс работает в среде Node.js, а окна получают отдельные renderer-процессы Chromium. В рекомендациях по безопасности отдельно подчёркиваются обновление Electron вместе с Chromium и Node.js, sandbox, context isolation, CSP и проверка IPC.
Для БОЛТУНа это означало бы поставлять собственную браузерную среду и аккуратно защищать ещё одну границу между интерфейсом и системой. Мы не стали заранее объявлять Electron «тяжёлым»: время запуска, RAM и CPU всё равно нужно сравнивать на одной машине. Но для небольшой постоянно работающей утилиты собственный Chromium оказался архитектурной ценой, которую наш прототип пока не оправдывал.
WinUI 3: современный нативный Windows-вариант
WinUI 3 был не кандидатом для галочки, а самой сильной нативной альтернативой. Microsoft рекомендует его для новых Windows-приложений: это C++ или C# с XAML, современными контролами и поддержкой Windows 10 версии 1809 и новее. Windows App SDK можно подключать и к существующим Win32-приложениям, а XAML Islands позволяют внедрять новый интерфейс постепенно.
Для нашего ядра на C++ это естественное соседство: меньше концептуальных слоёв между UI и Windows API, понятный путь к системным функциям и нативный визуальный стек. Если бы единственным критерием была интеграция с текущим Windows-клиентом, WinUI 3 мог бы оказаться первым выбором.
Цена — новый XAML-слой, особенности упаковки и развёртывания Windows App SDK и привязка нового интерфейса к Windows. Уже написанные TypeScript, CSS, SVG и браузерная логика не переезжают в WinUI напрямую. Для нашего эксперимента это означало сначала заново собрать UI, а уже потом проверять архитектурную границу с ядром.
Tauri 2: системный WebView и узкий Rust-bridge
Tauri занял промежуточную позицию. Его frontend остаётся веб-интерфейсом, backend пишется на Rust, а обмен между ними строится через команды и события. На Windows фреймворк использует установленный Microsoft Edge WebView2, то есть не кладёт ещё один Chromium в каждый дистрибутив. Общая схема с системным WebView описана в архитектуре Tauri.
Это позволяло повторно использовать нашу работу с TypeScript, CSS, SVG и motion JSON, а границу с ядром на C++ оформить небольшим Rust-адаптером. Для frontend можно явно ограничить доступ через capabilities и permissions и дополнительно зажать источники контента через CSP.
У Tauri тоже нет бесплатных чудес. Нужно учитывать наличие и версию WebView2, различия системных WebView на разных ОС, освоить Rust и самостоятельно спроектировать bridge к C++. Глобальный PTT, click-through overlay и высокочастотный звук от смены оболочки проще не становятся. А экономию памяти и размера ещё предстоит доказать замерами, а не логотипом фреймворка.
Если свести предварительный выбор к короткой таблице, получилось так:
Критерий | Electron | WinUI 3 | Tauri 2 |
|---|---|---|---|
Повторное использование нашего веб-интерфейса | максимальное | минимальное | максимальное |
Соседство с существующим C++/Win32-кодом | через нативный мост | наиболее прямое | через мост Rust/C++ |
Среда интерфейса | Chromium и Node.js в поставке | Windows App SDK | системный WebView2 |
Другие Desktop-ОС в перспективе | да | нет | да, с учётом различий WebView |
Главный риск для нас | цена runtime и поверхность IPC | повторная реализация UI и Windows-only | незавершённый native bridge и необходимость бенчмарков |
Мы выбрали Tauri 2 не потому, что он победил остальные варианты по всем пунктам. Для тестовой ветки он дал наиболее полезный компромисс: повторно использовать новый веб-интерфейс, не переносить аудиоядро, не поставлять собственный Chromium и проверить строгую границу WebView -> Rust -> C++. WinUI 3 остался запасным нативным вариантом, а Electron — понятной точкой сравнения для будущих замеров.
Почему для прототипа выбрали Tauri 2
Для проверки выбранной схемы мы создали ещё один изолированный проект внутри тестовой работы. Он не заменяет текущий Windows-клиент и не меняет стабильную сборку.
Целевая граница выглядит так:
┌────────────────────────────────────────────┐ │ Tauri WebView: представление │ │ │ │ комнаты · настройки · радио · SVG UI │ │ аватары · анимация · состояния кнопок │ └──────────────────────┬─────────────────────┘ │ команды и события ┌──────────────────────▼─────────────────────┐ │ Tauri 2 / Rust │ │ │ │ validation · lifecycle · capabilities │ │ адаптер к существующему native core │ └──────────────────────┬─────────────────────┘ │ узкий интерфейс ┌──────────────────────▼─────────────────────┐ │ Существующее ядро C++ / Win32 │ │ │ │ WASAPI · Opus · DSP · сеть · PTT · overlay │ └────────────────────────────────────────────┘
Главное правило: высокочастотное аудио не проходит через JSON IPC. WebView не должен получать PCM-буфер каждые 20 мс, рисовать из него бизнес-логику или решать, отправлять ли голос в комнату.
Интерфейс может сказать:
начать автокалибровку выбрать микрофон включить push-to-talk установить громкость участника
Нативное ядро может вернуть:
калибровка завершена шумовой фон: -52,4 dBFS рекомендуемый gain: 1,18 VAD threshold: 0,014
Но сами аудиокадры остаются в C++.
Из чего собрали прототип
В эксперименте используются:
Tauri CLI 2.11.4;
Rust 1.97.1 GNU;
TypeScript 7.0.2;
Vite 8.2.1;
установленный в Windows Microsoft Edge WebView2.
React мы не добавляли. Веб-клиент БОЛТУНа и так построен без тяжёлого UI-фреймворка, а для прототипа хватило TypeScript, CSS и Vite. В production-зависимостях интерфейса остался только @tauri-apps/api.
Tauri-часть тоже пока минимальна. Rust создаёт окно с отдельным каталогом данных WebView2 и предоставляет одну команду-заглушку get_runtime_status. Она доказывает работу первоначального IPC-моста, но ещё не подключает настоящее голосовое ядро на C++.
Это важное ограничение. Красивый экран комнат не доказывает, что Tauri-клиент умеет разговаривать.
Что уже получилось
Прототип воспроизвёл новый интерфейс тестовой ветки БОЛТУНа в отдельном frameless-окне: профиль, комнаты, участники, создание комнаты, настройки звука и панель радио.
Ещё в новом интерфейсе появился маленький анимированный человечек. К голосовой связи он напрямую не относится — это скорее небольшая живая деталь, чтобы окно не выглядело совсем статичным. В Tauri мы собрали его из SVG-частей, поэтому он остаётся чётким при разном масштабе. Человечек умеет ходить, целиться, уклоняться, получать попадания и падать.
Мы начинали с простой анимации, а в итоге неожиданно для себя получили первый опыт создания маленькой игры внутри БОЛТУНа. Но это уже отдельная история для следующей статьи.
Сборка Tauri в режиме release запустилась в нативном WebView2-окне. SVG и motion JSON загружаются под CSP, окно корректно сворачивается, разворачивается, восстанавливается и закрывается. Прототип остался полностью изолирован от рабочего клиента.
Для доступа frontend мы выдали только минимальные capabilities:
{ "permissions": [ "core:default", "core:window:allow-minimize", "core:window:allow-toggle-maximize", "core:window:allow-close" ] }
В CSP нет разрешения на произвольные внешние скрипты, shell или файловую систему. Когда появятся настоящие команды ядра, каждую придётся добавлять явно и проверять её входные данные на Rust/native-стороне.
Автокалибровка как проверка архитектурной границы
В интерфейсе тестовой ветки БОЛТУНа есть кнопка «Автокалибровка · 3 сек». В Tauri-прототипе она пока демонстрирует состояние UI. В тестовой версии настоящего клиента за ней уже работает нативный алгоритм на Windows и аналогичная реализация на Android.
Эта функция хорошо показывает, почему мы не хотим переносить весь код в WebView.
Пользователь не должен знать, что такое VAD threshold, dBFS и входной gain. Он нажимает кнопку и три секунды говорит обычным голосом. Приложение должно подобрать чувствительность так, чтобы не отправлять шум комнаты и не отрезать тихие начала слов.
Никакой нейросети и облачного распознавания здесь нет. Калибровка выполняется локально на PCM:
WASAPI / AudioRecord ↓ 20-миллисекундные PCM-кадры ↓ RMS каждого кадра + общий peak ↓ оценка шумового фона и голосовых кадров ↓ рекомендованные input gain и VAD threshold
Содержание речи не распознаётся и на сервер не отправляется. В настройках сохраняются только имя устройства, уровни в децибелах и подобранные коэффициенты.
Три секунды быстро, минута осторожно
Одного трёхсекундного окна мало. Пользователь мог нажать кнопку и не успеть заговорить. Поэтому в тестовой версии появилась двухэтапная схема:
Быстрая фаза собирает три секунды и сразу даёт пригодную настройку.
Фоновая фаза продолжает анализ ещё 57 секунд и уточняет профиль по первой минуте разговора.
Быстрый результат применяется сразу. Финальный — плавно, чтобы порог VAD не перескочил посреди слова. На каждом цикле ядро приближает текущие значения к рассчитанным на 3,5% разницы:
appliedGain += (targetGain - appliedGain) * 0.035f; appliedVad += (targetVad - appliedVad) * 0.035f;
При push-to-talk в голосовую выборку попадают только кадры, снятые во время удержания назначенной кнопки. Иначе минута честного молчания могла бы убедить калибровщик, что голос у пользователя отсутствует.
После смены микрофона сохранённый профиль сбрасывается. Калибровка встроенного микрофона ноутбука не должна автоматически применяться к USB-гарнитуре.
Почему не стоит писать третий калибровщик на TypeScript
На Windows алгоритм реализован на C++, на Android — на Java рядом с AudioRecord. Формулы близки, но уже имеют небольшие платформенные различия. Например, Android оценивает рекомендуемый VAD из raw RMS и текущего gain, а Windows использует уровень, который видит интерфейс.
Если перенести логику в Tauri frontend, появится третья реализация. Она будет зависеть от частоты обновления UI, WebView lifecycle и сериализации. Со временем три клиента начнут по-разному понимать одну и ту же кнопку «Автокалибровка».
Поэтому для Tauri мы хотим оставить простой контракт:
UI -> begin_microphone_calibration(device_id) native -> calibration_progress(seconds) native -> calibration_finished({ noiseFloorDb, voiceRmsDb, peakDb, inputGain, vadThreshold, voiceDetected, clippingDetected })
PCM, процентили и изменение gain остаются в нативном аудиопути. WebView только показывает процесс и результат.
Что прототип пока не доказал
На текущем этапе таблица выглядит так:
Проверка | Статус |
|---|---|
Release-сборка Tauri 2 собирается и запускается | проверено |
Новый SVG-интерфейс работает в WebView2 | проверено |
Окно без рамки сворачивается, разворачивается и закрывается | проверено |
SVG-человечек и motion JSON под CSP | проверено |
Настоящий мост к голосовому ядру на C++ | пока заглушка |
Автокалибровка из Tauri вызывает ядро на C++ | не подключено |
PTT при неактивном окне и поверх игры | не проверено |
Существующий click-through overlay сохранён | не проверено |
Трей и запуск в одном экземпляре | не завершено |
Сравнение времени запуска, RAM и CPU | не проведено |
DPI 100/125/150% на одной тестовой машине | требует итогового прогона |
План замеров уже составлен. Текущий клиент и Tauri-прототип нужно запустить на одной Windows-машине и сравнить холодный и повторный запуск, RAM и CPU в простое, работу радио, анимации, комнат и настроек. Отдельно проверить PTT при неактивном окне и поведение оверлея.
Пока таблица результатов пуста, утверждение «Tauri легче и быстрее Win32» было бы рекламой. Цель эксперимента скромнее: выяснить, достаточно ли хорош WebView как новый слой представления и можем ли мы подключить его, не ухудшив голос.
Как будет выглядеть миграция, если тест пройдёт
Мы не планируем одним коммитом заменить всё приложение.
Первый этап — подключить Tauri UI к существующему ядру на C++ через узкий адаптер. Rust отвечает за проверку команд, жизненный цикл приложения и разрешения. C++ продолжает захватывать и воспроизводить звук, кодировать Opus, работать с сетью и глобальными клавишами.
Второй этап — перенести в Rust только те системные сервисы, для которых появится измеримая польза. Не потому, что Rust «правильнее», а потому, что конкретный модуль станет безопаснее или проще сопровождать.
Третий этап — отказаться от старого Win32 GUI только после функционального паритета, сравнительных замеров и полевого теста с игрой.
Оверлей может вообще остаться отдельным нативным окном. Его текущие свойства — topmost, click-through и отсутствие фокуса — важнее единообразия технологии.
Что мы вынесли из обсуждения
Обсуждение не доказало, что Win32 был ошибкой. Первая версия благодаря ему вообще появилась и получила рабочий PTT, оверлей и звук без многомесячного фреймворк-проекта.
Но обсуждение помогло правильно сформулировать накопившуюся проблему. Нам не обязательно выбирать между двумя крайностями:
оставить весь интерфейс на Win32 навсегда или переписать всё приложение вместе со звуком
Можно заменить представление, сохранив real-time ядро. Tauri 2 здесь не новая религия, а проверяемая гипотеза.
Сейчас результат такой: тестовая ветка уже доказала, что новый интерфейс БОЛТУНа можно собрать в Tauri/WebView2, сохранить векторную графику и изолировать эксперимент от стабильной версии. Она ещё не доказала, что Tauri готов заменить рабочий Win32-клиент в игре.
Именно поэтому следующий шаг — не дорисовывать ещё одну красивую панель, а подключить настоящий мост к ядру на C++, вызвать через него ту же автокалибровку и измерить, замечает ли аудиопоток существование WebView.
Если не замечает — у эксперимента есть будущее.
Проект: БОЛТУН
Отдельное спасибо Alexme — именно он настоял на внедрении нового фреймворка и подтолкнул нас к этому эксперименту.
Команда: PAPA DAN — разработчик; Alexme — главный тестировщик; Mihanz, San4o и A1ex — участники командных проверок и обратной связи.

