Исследование · июль 2026

Как озвучивать инженерные тексты

Тексты с кодом, нотацией и терминами не читаются вслух так же, как читаются глазами. Пятнадцать раундов измерений: что помогло, что не помогло и почему очевидные ходы оказались ловушками.

Результат

0.8%
CER — потеря символов при обратном распознавании
было 25.3%
114
слов в минуту — темп чтеца в референсе
было 160
1.8%
тишины — речь не рвётся на куски
было 20% у прежнего движка
СталоQwen3-TTS, тот же референсный клип, текст короткими предложениями, синтез одним проходом
БылоESpeech PODCASTER, синтетический референс, плоский текст
Путь по раундам, CER одного образца
Каждый раунд менял одну вещь. Ни одно улучшение не было получено сменой движка на «более новый».

Задача

Тренажёру подготовки к бэкенд-собеседованиям нужен аудиорежим — заниматься на прогулке, как слушают подкаст. Контент есть: 602 вопроса, у каждого разбор с эталонным ответом, ловушками и следующими вопросами. Итого 473 тысячи символов, около 9.5 часов речи.

Ограничения: генерация полностью локальная и офлайн, без провайдерских ключей на сервере. Язык — русский. Материал — инженерный: код, SQL, аннотации, аббревиатуры.

Раунд 1–2. Движки

Проверено семь вариантов, все локальные. Скорость проблемой не оказалась ни у одного: даже самый медленный проходит корпус за ночь, а быстрые — за четверть часа.

Разборчивость по движкам, CER на подготовленном тексте
среднее по трём образцам
Меньше — лучше. Но эта шкала описывает только чёткость: три самых чистых движка были отвергнуты на слух как механические.
Piper · dmitri×40 CPU · «очень плохо»
Silero v4 · baya×65 CPU · «робот»
Silero v5 · kseniya×30 CPU · «чётко, но сухо»
Vosk-TTS 0.9×2 CPU · «очень чётко, но роботично»

Почему первый выбор был худшим

Открытый лидерборд русского TTS показал, что Silero v4 стоит на последнем месте из 34 записей по натуральности — и объективно хуже собственной версии 3.1. То есть новее не значит лучше.

UTMOS — предсказанная натуральность, больше лучше
локальныеоблачные
Оговорка: UTMOS обучен в основном на английском. Piper Irina получил 3.67 — почти уровень облачных сервисов — и был отвергнут ухом первым. Метрика указывает направление, но не решает.

Раунд 3. Метрика важнее слуха

«ESpeech понравился больше всего по темпу и стилю, но съедает часть букв: "Слушай вопрос" звучит как "Слушай вапас"».

Пока дефект оставался впечатлением, чинить его было нечем. Решение: прогонять синтез обратно через распознавание и считать CER — долю потерянных символов.

Это оказалось не только измерением, но и защитой. Silero молча выбрасывает латиницу:

ФразаОжидаемоSilero выдал
List<? extends Number> удобно читать как Number~3.4 с1.1 с
Более безопасные варианты — ON DELETE RESTRICT~3.3 с1.7 с
Сырой текст, Sileroполовина содержания исчезла беззвучно
Референсный голосLibriVox, общественное достояние — 13 секунд, задающие голос всего курса

Три движка ведут себя на латинице по-разному, и это влияет на дизайн сборки: Silero выбрасывает молча, Vosk падает с KeyError, Piper читает с русской фонетикой. Худшее поведение — именно молчаливое.

Что помогло

PODCASTER против RL-V2 на трёх образцах
PODCASTERRL-V2
RL-V2 дообучен через RL именно на разборчивость. Ровно вдвое лучше, ценой смены файла весов.

Второй рычаг оказался неожиданно сильным. ESpeech клонирует голос по образцу на 10–13 секунд, и первые прогоны использовали образец, сгенерированный Silero — то есть модель просили копировать артикуляцию у робота, которого сама же работа и забраковала.

Один и тот же текст, шесть человеческих референсов
CER, %
Восьмикратный разброс при прочих равных. Референс — не косметика, а главная настройка zero-shot модели, и подбирается замером, а не на слух.

Что не помогло

Раунд 4. Текст, который нельзя читать вслух

«Текст подготовлен плохо. "Мнемоника называется ПЕКС: продюсер экстендс, консьюмер супер" — это просто перечисление терминов. Преподаватель никогда не читает листинг кода, как и подкастеры».

Здесь чинилась не та проблема. Я считал, что беда в непроизносимости нотации, и написал слой нормализации: развернуть, кириллизовать, произнести. Замерил распространённость — паттерны есть у 72% вопросов корпуса:

Доля вопросов с непроизносимой нотацией
доля корпуса, %
Большинство — словарные случаи. Структурной переписки требуют единицы процентов, но именно они ломают восприятие сильнее всего.

Текст стал произносимым — и остался плохим, потому что это был тот же листинг, просто озвученный. Нотацию нужно переводить не в звук, а в смысл.

Было. Лист, вайлдкард экстендс Намбер. Из такого списка удобно читать, каждый элемент точно является Намбером. А вот добавить туда Интеджер нельзя. Мнемоника называется ПЕКС: продюсер экстендс, консьюмер супер.
Стало. Случай первый. Коллекция для тебя — источник. Ты из неё только читаешь. Тогда ты объявляешь верхнюю границу. Читать отсюда безопасно: что ни достанешь — это точно число. А вот положить внутрь нельзя ничего. И вот причина, ради которой этот вопрос и задают: компилятор не знает, чей это список на самом деле. Может, целых чисел, а может, дробных.

Появилось и то, чего в исходном тексте не было вообще: возврат к отложенной мысли. В начале — «запомни оговорку, вернёмся к ней», в конце про ловушку со стиранием типов — «вот здесь и срабатывает та самая оговорка». На бумаге избыточно: глаз вернётся абзацем выше. В ухе возврата нет, и это единственный способ связать части объяснения.

Полный свод правил — ADR 0002. Побочный эффект: аудио-версия примерно в 1.8 раза длиннее экранной, объём корпуса вырос с 9.5 до 16.5 часов.

Раунд 4, продолжение. Скрипт становится партитурой

Второе замечание того же раунда — монотонность. Плоская строка не умеет замедляться, поэтому единицей скрипта стала реплика с типом, несущая свой темп и паузы.

Тип репликиСкоростьПауза доПауза после
повествование, связки×1.02120 мс120 мс
термин, новый случай×0.88420 мс260 мс
ключевая фраза×0.86480 мс420 мс
вопрос×0.92300 мс250 мс

Таблица живёт в коде одним объектом: правка в одном месте меняет дикцию всего курса. Паузы вставляются реальными сэмплами при склейке, а не выпрашиваются у модели.

С ритмом163 с · CER 6.2% · замедления на терминах
Ровным потоком141 с · CER 7.3% · тот же текст, одна скорость

23 секунды осмысленной медлительности — и разборчивость не просела, а выросла. Замедление на терминах помогает не только человеку, но и распознавалке.

Раунд 5. Паузы и темп

«Паузы слишком резкие, будто обрезается голос. Кроме того, скорость слишком высокая, сложно воспринимать такую скорость для инженерных текстов».

Почему паузы резали

Замер огибающей на стыках показал механизм: перед паузой сигнал держится около 0.013 — это собственный шумовой фон референсной записи, примерно −30 dB от пика. В паузе — ровно 0.0000. Переход занимает один сэмпл.

Ухо слышит здесь не «говорящий замолчал», а «запись выключилась»: пропадает не только голос, но и воздух помещения.

Вариант паузыСкачок между сэмпламиПерепад громкостиCER
цифровой ноль0.00208×256.3%
+ затухание 45 мс0.00037×234.9%
+ фон комнаты0.00058×124.6%
Цифровой нольисходная проблема
Затуханиеразрыв в 5.6 раза меньше
Фон комнатыв паузе остаётся воздух записи

Насколько быстро мы говорили

Темп меряется в слогах в секунду — для русского это просто количество гласных. В нашем корпусе 2.37 слога на слово, что даёт перевод в привычные слова в минуту.

Чтослог/сслов/мин
Наша озвучка на ×1.05.6–6.3142–160
Средний темп русской речи4.73120
Технические презентации (англ. источники)2.5–3.25100–130
Учебное объяснение2.4–3.960–100

Инженерный текст озвучивался на скорости развлекательного подкаста. Две традиции меряют в разных единицах, но в слогах в секунду сходятся: 2.5–4.0 для материала, который нужно усвоить, а не прослушать.

Главный отрицательный результат

Очевидный ход — покрутить ручку speed у модели. Она масштабирует предсказанные длительности и выводит модель за пределы обучающего распределения: речь плывёт.

Три способа снизить темп: цена в разборчивости
ручка speed модели растяжение atempo больше пауз
Влево — медленнее, вниз — разборчивее. Ручка модели уходит вверх: замедление до целевого темпа роняет разборчивость впятеро. Два других механизма держат линию.
Ручка модели, 111 слов/минCER 13.8%
Растяжение, 113 слов/минCER 5.3%
Паузы ×3, 104 слов/минCER 5.3%

При одном и том же темпе паузы и растяжение дают разборчивость в 2.6–4.4 раза лучше, чем ручка модели. Финальная комбинация — паузы ×3, затем растяжение 0.92: 96 слов/мин при CER 5.2%.

Порядок важен: паузы даются первыми, потому что они несут смысл — границу мысли. Растяжение остаётся косметической доводкой до целевой цифры. Ровно так делает живой преподаватель: он не растягивает слова, он молчит между мыслями.

Раунд 6. Пауза принадлежит границе

«Паузы слишком длительные и неестественные, они звучат неестественно, появляясь на полуслове».

Замер оказался хуже ожидаемого: 34% файла — тишина, медианная пауза 2.15 секунды, самая длинная — 9.3 секунды. И это при том, что предыдущий раунд уже занимался паузами и считал вопрос закрытым.

Четыре источника тишины перемножились

Ошибка была в модели, а не в числах:

Арифметика сходится в точку: 260 + 250 + 120 = 630 мс, ×3 = 1890, ×1.087 = 2055 мс — ровно наблюдавшаяся медиана. Пауза на подумать в 2.5 с тем же путём стала 9.3 с. А поскольку раздувалось всё одинаково, двухсекундная пауза вставала внутри одной мысли — отсюда обрыв на полуслове.

Длина берётся из границы

Граница между репликамиПауза
повествование продолжается140 мс — вдох, а не остановка
после термина идёт его объяснение260 мс
подводка к термину или ключевой фразе480 мс
после ключевой фразы700 мс
пауза на устный ответ2500 мс — единственная намеренно длинная

Плюс два следствия: никаких глобальных множителей, и растяжение применяется к сегментам речи до сборки — так пауза длится ровно столько, сколько заявлено, при любом темпе.

12.3%
доля тишины в файле
было 33.8%
605 мс
медианная пауза
было 2155 мс
33
пауз — столько же, сколько было
менялись только длительности

Расстановка пауз была верной с самого начала. Максимальная пауза сократилась с 9.3 с до 2.9 с — и это ровно та единственная, что даётся на устный ответ.

Было · паузы ×334% тишины · медиана 2.15 с · CER 5.2%
Стало · 98 слов/мин12.3% тишины · медиана 0.61 с · CER 3.9%
Стало · 114 слов/минлучший CER исследования · CER 2.2%
Лестница темпа с новой моделью пауз
CER, %
Разборчивость улучшилась и сама по себе: раньше atempo обрабатывал вместе с речью и фон комнаты, добавляя артефакты.

Мораль. Симптом «звучит неестественно» почти никогда не лечится подбором числа. Подкрутить множитель здесь означало бы получить то же самое, только тише. Настоящая ошибка была в том, что длительность промежутка вообще никем не задавалась осознанно — она получалась побочным эффектом сложения.

Это третий раз за исследование, когда правильным ходом оказалось поменять модель, а не параметр: до этого был текст (транслитерация вместо объяснения) и темп (ручка модели вместо пауз и постобработки).

Раунд 7. Края фраз и нерегулярность

«На краях фразы паузы звучат супер неестественно, они съедают буквы. Кроме того, человек не делает всегда одинаковые паузы и в одних и тех же местах».

Затухание съедало согласные

Затухание 45 мс из шестого раунда применялось ко всему сегменту, включая речь. Замер энергии на краях: у отдельных реплик в первых 45 мс сигнал достигает 83% от собственного пика. Русский взрывной согласный длится 50–80 мс — то есть косинусная кривая просто стирала начальный звук.

12 из 26
реплик с живым сигналом в зоне затухания
почти половина фраз
8 мс
новая длина затухания
было 45 мс
23
паузы, 93–2500 мс, медиана 342
было 25 одинаковых

Тупик по дороге

Сначала я сделал «умнее»: находить реальные границы речи по энергии, обрезать лишнее и затухать только в тихой зоне. Стало хуже. Причина поучительная: глухие щелевые — «с», «ш», «ф», мягкое «ть» — энергетически лежат далеко ниже любого разумного порога. Порог срезал ровно те согласные, ради спасения которых всё и затевалось.

Ablation: обрезка по порогу против простого короткого затухания
с обрезкой по порогу без обрезки без обрезки и без разброса темпа
Простое короткое затухание оказалось безопаснее пороговой эвристики, особенно на медленном варианте: 7.9% против 3.4%.

Паузы перестали быть метрономом

Базовая длительность теперь умножается на случайное число из 0.62–1.42, а граница уровня «вдох» с вероятностью 35% не получает паузы вовсе — фразы сливаются. Генератор засевается хешем текста соседних реплик, поэтому одинаковый контент даёт одинаковое аудио при любой пересборке.

Было · затухание 45 мс, паузы по таблице25 одинаковых пауз
Стало · затухание 8 мс, паузы с разбросом23 паузы, 93–2500 мс · CER 3.4%

Честно про метрику

CER после этих правок — 3.4–4.8% против 2.2–4.8% до них. Разброс между конфигурациями сопоставим с разбросом самого замера на одном образце, поэтому улучшение разборчивости здесь не заявляется.

Правка краёв обоснована прямым измерением сигнала, а не метрикой. Это тот случай, где CER показывает отсутствие вреда, но не способен показать пользу: он меряет, разобрано ли слово, а не то, звучит ли начало фразы обрубленным. Три раунда назад метрика поймала дефект, которого не слышало ухо; здесь ухо поймало дефект, которого не видит метрика.

Раунд 8. Убираем искусственные паузы совсем

«Мне не нравятся искусственные паузы совсем, давай попробуем их вообще убрать, как было без них».

Правильный вывод, и стоило прийти к нему раньше. Три итерации подряд я правил один и тот же механизм: паузы были слишком резкими, потом слишком длинными, потом слишком одинаковыми. Каждая правка улучшала замеры. Каждый результат на слух оставался искусственным.

Три раунда правок одного механизма — сигнал, что механизм не нужен, а не что он недонастроен.

Из конвейера ушли таблица длительностей, функция границы, разброс, пропуск части пауз и фон комнаты. Осталось затухание 8 мс на стыках — это анти-клик, а не пауза.

Два способа не вставлять паузы
сплошной синтез всего текста реплики стык в стык
Пауз не вставляется ни в одном варианте. Разница только в том, кто решает, где резать текст на куски для синтеза.
Сплошной синтез · 99 слов/мин13 пауз, 1.3% тишины · CER 6.4%
Реплики стык в стык · 97 слов/мин35 пауз, 7.1% тишины · CER 3.2%

Неожиданное

Сплошной синтез оказался вдвое хуже по разборчивости — при том, что пауз не вставляется ни там, ни там.

Причина: F5 сам режет длинный текст на куски по числу символов и сшивает кросс-фейдом. Его граница попадает в середину фразы, наша — на границу смысла. То есть разбиение на реплики полезно не только для темпа, но и как способ отобрать у модели право резать текст где попало.

Побочно объяснилось, почему сплошной вариант звучит безостановочно: 13 пауз против 34 и 1.3% тишины против 7.1%. Длинный непрерывный текст модель читает не дыша.

Мораль. Пауза теперь — свойство пунктуации в тексте, а не параметр сборки. Хочешь паузу — поставь точку и начни новую реплику. Это и проще, и честнее: длина паузы становится решением автора текста, а не подобранным числом в конфиге.

Раунд 9. Темп принадлежит референсу

«Ты искусственно замедлил звук?»

Да. atempo=0.66 — растяжение готового аудио в полтора раза. И это выходило далеко за границу осторожности, которую я сам же записал двумя раундами раньше: «сильнее 0.8 — осторожно, артефакты слышны, хотя CER их не показывает».

Вопрос вскрыл две ошибки подряд.

Ошибка первая: референс отбирался только по разборчивости

F5 — zero-shot модель. Она копирует из референса не только тембр, но и темп речи. Отбирая референс, я смотрел исключительно на CER и ни разу не померил, с какой скоростью говорит чтец.

Темп речи в референсных записях
слов в минуту
Разброс полуторакратный. Я взял одного из самых быстрых — и потом полтора раза растягивал результат, чтобы попасть в учебную норму.

Ошибка вторая: клипы резались по таймеру

Первые референсы вырезались окном в 13 секунд с отметки 45 с, чтобы проскочить дисклеймер LibriVox. Окно попадало в середину фразы, и транскрипт получался обрубком: «улиц губернского города о дело происходило в 1842 году».

F5 получает транскрипт референса дословно. Кривой транскрипт портит каждый синтезируемый сегмент, а не только начало. Именно поэтому Тургенев в первом отборе выглядел плохо и был отвергнут.

Референс, нарезанный по фразамТургенев, 74 слов/мин — исходный клип
Результат · 91 слов/минбез растяжения, 142 паузы, все модельные · CER 1.0%
Референснарезкарастяжениеслов/минпаузCER
Лесковпо таймеру×0.6697353.2%
Тургеневпо таймеру×1.001081367.3%
Тургеневпо фразам×1.00911421.0%
Достоевскийпо фразам×1.001332711.2%

Одна только чистая нарезка того же клипа дала 7.3% → 1.0%, семикратное улучшение.

Мораль. Четыре раунда подряд я компенсировал следствие вместо причины: паузы правил затуханиями, темп — растяжением. Оба свойства принадлежали референсу, и оба чинились в постобработке. Признак, который стоило заметить раньше: если параметр приходится подкручивать всё сильнее, скорее всего его источник находится выше по конвейеру. С 45 мс затухания до 8 мс, с ×0.9 растяжения до ×0.66 — оба раза цифра уползала в крайность, и оба раза чинилось не то место.

Раунд 10. Фразу — на одном вдохе

«Фраза "И вот причина ради которой этот вопрос и задают" читается так: "И вот причина" / "ради которой этот вопрос" / "и задают". Ожидается, что её прочитают на одном вдохе».

Реплика короткая — 48 символов, внутреннему разбиению модели не подвергается. Значит разрывы приходят из просодии, а просодию модель берёт из референса.

Разрывы внутри фразы, с запятой
разрывов ≥100 мс внутри фразы
Выбранный референс давал три разрыва — 105, 355 и 295 мс, ровно четыре куска, в точности как описано в отзыве. Удаление запятой снимает по разрыву у каждого чтеца.
Было · 3 разрываТургенев, «Дворянское гнездо»
Тот же чтец без запятой2 разрыва — мало помогает
Стало · 0 разрывов«Белые ночи», без запятой

Ловушка: слитность в одиночку не оптимизируется

Казалось бы, взять самого слитного — «Поездку в Полесье», ноль пауз в минуту. На полном тексте он дал CER 18.8%. Слитно и неразборчиво. Второй по слитности — 14.1%.

Референсслов/минтишиныCER
Полесье — самый слитный1091.2%18.8%
Записки из подполья1333.5%14.1%
Белые ночи1009.1%1.7%

«Слитный» и «разборчивый» оказались независимыми свойствами — ровно как до этого темп с разборчивостью. Третий раз за исследование одна и та же ловушка: улучшаешь один показатель референса, роняешь другой. Референс отбирается по трём измерениям сразу.

Правило для текста

Реплики типов «ключевая фраза», «термин» и «вопрос» короткие по построению и должны звучать на одном вдохе — значит внутренних запятых в аудио-версии у них нет. Затронуто 6 реплик из 26. Это продолжение правила восьмого раунда: если пауза — свойство пунктуации, то и отсутствие паузы задаётся пунктуацией.

Побочный результат, который стоит признать

С хорошим референсом сплошной синтез перестал проигрывать разбивке по репликам — 1.0% против 1.2% — и оказался вдвое слитнее: 21 пауза против 38. Вывод восьмого раунда о превосходстве разбивки был получен на плохом референсе и держался именно на нём.

Конвейер после десяти итераций свёлся к текст → синтез одним проходом. Ни вставленных пауз, ни растяжения, ни разбивки, ни единого параметра, подобранного на слух. Осталось правильно написанный текст и правильно выбранные тринадцать секунд референса.

Раунд 11. Тембр и манера — разные уровни

«Предыдущий чтец по тембру голоса мне нравился больше. Так же есть ощущение, что с пунктуацией надо продолжить работу. И с текстом нужно поработать ещё, чтобы легче воспринимался на слух».

Первое замечание вскрыло ошибку в самой процедуре. До сих пор при любой проблеме с манерой речи я менял чтеца целиком — вместе с тембром. Тургенев рвал фразы, я взял Достоевского, и тембр уехал вместе с манерой.

Наблюдение

Тембр принадлежит голосу и постоянен. Манера принадлежит отрывку и меняется по ходу записи: один и тот же человек читает описание слитно, а диалог — рвано.

Скан всей записи — 777 секунд, 255 сегментов, 35 подходящих окон — показал, что плотность пауз у одного и того же чтеца гуляет от 6.7 до 20 с лишним в минуту. Прежний клип просто попал в рваный участок.

Отрывок того же чтецапауз/мин у клипаразрывов внутри фразы
прежний, 51 с13.33
t0 — 51.5 с6.71
t1 — 463 с11.43
t2 — 193.5 с12.70

Отдельно поучительно: плотность пауз в клипе не предсказывает поведение на нашей фразе. У t2 этот показатель хуже, чем у t0, а фразу он читает слитно. Косвенный показатель не заменяет прямую пробу — контрольную фразу нужно синтезировать каждым кандидатом и считать разрывы в ней.

Прежний отрывок · 3 разрыватот же голос, рваный участок записи
Отрывок t0 · 1 разрывсамый слитный по показателю клипа
Отрывок t2 · 0 разрывоввыбран по прямой пробе

Процедура выбора стала двухступенчатой: голос выбирает человек по тембру, отрывок выбирает скрипт по пробе.

Текст

Правило «без внутренних запятых» из прошлого раунда касалось только ключевых фраз. Распространено на весь текст: одна мысль — одно предложение; вложенные придаточные разбиваются; запятая, тире и двоеточие внутри фразы не используются; подлежащее в начале.

Сложность текста на слух
было стало
Дело не только в паузах. Придаточное требует удерживать начало фразы в голове до её конца: на бумаге это бесплатно, в ухе стоит дорого.
Было. Обычно их объясняют через мнемонику из четырёх английских слов, но заучивать её бесполезно, если не понимаешь, откуда она берётся.
Стало. Их обычно объясняют мнемоникой из четырёх английских слов. Заучивать её бесполезно. Мы зайдём со стороны смысла.

Три кандидата на одном голосе

t2, без обработки118 слов/мин · медиана паузы 170 мс · CER 1.1%
t2, растяжение 0.88103 слов/мин · медиана 242 мс · CER 1.5%
t0, по репликам102 слов/мин · медиана 450 мс · CER 0.9%

Компромисс сместился, но не исчез: t2 звучит слитнее — медианная пауза вдвое короче, — а t0 точнее по метрике и попадает в темп без всякой обработки. Мягкое растяжение 0.88 вернулось как допустимая доводка: оно внутри безопасной зоны, в отличие от отвергнутого 0.66. Разница между инструментом и костылём здесь ровно в глубине.

Раунд 12. Обзор, который надо было пересмотреть раньше

«Мне кажется, мы провели недостаточно хорошее исследование. Нужно попробовать и другие русские и новые модели».

Справедливо, и хуже, чем кажется на первый взгляд.

Две ошибки

Первая. Отбор проверил семь движков и остановился, как только один заработал приемлемо. Дальше десять раундов улучшали выбранный движок, ни разу не спросив, не решает ли задачу другой лучше.

Вторая. Отбор опирался на открытый лидерборд русского TTS. При повторной проверке оказалось, что он не обновлялся с августа 2025 года. Всё, что вышло позже, отсутствует в нём по построению — а вышло немало:

МодельДатаЛицензия
Qwen3-TTS (Alibaba)январь 2026Apache-2.0
VibeVoice (Microsoft)январь 2026MIT
IndexTTS-2январь 2026—
Higgs Audio v3 (Boson AI)2026открытая
CosyVoice 32026открытая

Замер на одном материале

Рваность речи: F5/DiT против LM-архитектуры
ESpeech RL-V2 · F5/DiT Qwen3-TTS · LM
Один и тот же референсный клип, один и тот же текст. Медиана паузы показана в сотых долях секунды, чтобы уместиться на одну шкалу с остальными величинами.
Движокслов/минпаузтишинымедианаCERскорость
Qwen3-TTS, одним проходом114381.8%72 мс0.8%×0.4
Qwen3-TTS, по репликам109804.2%75 мс1.2%×0.4
ESpeech RL-V211812320.1%180 мс1.0%×4.1
ESpeech RL-V111811820.3%225 мс—×4.1
ESpeech PODCASTER11811822.3%250 мс2.3%×4.1
ESpeech RL-V2 · F5/DiT123 паузы · 20.1% тишины · медиана 180 мс
Qwen3-TTS · LM38 пауз · 1.8% тишины · медиана 72 мс · CER 0.8%

Главное, и оно неприятное

Три разных чекпойнта ESpeech — RL-V2, RL-V1, PODCASTER — дают практически одинаковые числа. Разница между ними в пределах погрешности.

Qwen на том же референсе и том же тексте: 38 пауз вместо 120, 1.8% тишины вместо 20%, медианная пауза 72 мс вместо 180–250.

Значит рваность, с которой шла борьба четыре раунда подряд — вставленными паузами, подбором затуханий, перебором отрывков записи, переписыванием пунктуации, — была свойством архитектуры F5/DiT. Не настроек. Не референса. Не текста. Qwen3-TTS построен иначе: дискретная многокодбуковая языковая модель без DiT, и этой проблемы у него просто нет.

Четыре раунда лечили симптом чужого инструмента.

Цена и оговорки

×0.4 к реальному времени против ×4.1 — вдесятеро медленнее. Корпус в 16.5 часов: около 41 часа генерации вместо четырёх. Разово это две ночи. Замер сделан без flash-attn, который рекомендует документация Qwen; с ним разрыв должен сократиться, насколько — не проверено.

Непроверенным осталось многое: VibeVoice, IndexTTS-2, Higgs Audio v3, CosyVoice 3, Chatterbox, XTTS-v2, а также веса ESpeech SFT-95K и SFT-256K и русский F5 от Misha24-10 (последние три не скачались из-за таймаутов зеркала). По этим архитектурам вывода нет.

Урок. Ошибка не в том, что первый выбор оказался неоптимальным — это нормально и неизбежно. Ошибка в том, что после выбора обзор ни разу не пересматривался, хотя работа шла десять раундов и раз за разом упиралась в одну и ту же проблему. Признак тот же, что и в девятом раунде, только на уровень выше: если несколько итераций подряд борются с одним симптомом и каждая помогает лишь частично, источник, скорее всего, вне конвейера вообще. И отдельно: внешний рейтинг — снимок на дату, и дату надо смотреть раньше, чем цифры.

Раунд 13. Ударения: что можно и чего нельзя

«Qwen3-TTS это что-то невероятное, качество очень похожее на реального человека. Единственное — не везде корректно поставлены ударения».

Ограничение известное и документированное: Qwen3-TTS обучался без разметки ударений, поэтому пользовательские знаки ударения он не поддерживает. В репозитории модели по этому поводу открыты обсуждения.

Но «не поддерживает» — не то же, что «игнорирует». Проверил четыре нотации на пяти фразах, измеряя не «стало ли лучше» (это на слух), а не сломался ли текст — обратным распознаванием.

Порча текста от разметки ударений
худший CER среди пяти фраз
Обе стандартные нотации непригодны. Плюс особенно неприятен тем, что не игнорируется, а озвучивается: «этот вопрос сто плюс ит разобрать».
С плюсами · CER 64%«Компил плюс ятор провер в яяе т плюс ипы»
С ЗАГЛАВНЫМИ · CER 0%текст цел, длительность меняется — модель реагирует

Что делать

Надёжный способ — переписывать. Слово с неоднозначным ударением заменяется на однозначное:

ПроблемноеЗамена
«этот вопрос стоит разобрать»«этот вопрос надо разобрать»
«большая часть проверок»«основная часть проверок»
«список параметризован типом»«у списка задан тип элемента»

Это продолжение принципа второго раунда: аудио-версия — отдельный жанр, и слова в ней выбираются по звучанию, а не только по смыслу. Омографы туда просто не попадают.

Второй способ — ЗАГЛАВНЫЕ, точечно. Единственная нотация, не портящая текст, и модель на неё реагирует: длительность фразы меняется с 2.56 до 2.88 с. Двигает ли она ударение в нужную сторону — надо слушать.

Чего сделать не удалось

Хотел померить ударения автоматически. Написал детектор: границы слов из Whisper, внутри слова — деление огибающей на вокалические ядра, ударное — с наибольшим произведением громкости на длительность, ожидание — из RUAccent.

На записи Qwen он показал 61% расхождений. Цифра выглядела неправдоподобно, а ошибки подозрительно смещались к первому слогу — поэтому я прогнал детектор по контрольному материалу: живой записи чтеца, где ударения верны по определению.

61%
«расхождений» у синтеза
по показаниям детектора
70%
«расхождений» у живого человека
где ударения верны по определению

Инструмент меряет не ударение, а положение взрывного согласного в начале слова. Его числа не используются нигде, включая те 61%. Скрипт лежит в репозитории как отрицательный результат. Помогло бы настоящее фонемное выравнивание вместо энергетической эвристики — не делалось.

Заметка про метод. Единственная причина, по которой ошибочные 61% не попали в отчёт, — детектор был прогнан по контролю прежде, чем ему поверили. Ровно этой проверки не было сделано десятью раундами раньше в отношении внешнего лидерборда: там не посмотрели дату, и это стоило проекту десяти раундов работы не с тем движком. Навык один и тот же — прежде чем верить измерению, проверь измеритель.

Продолжение — раунд 23. Позже модель научили слушаться знака ударения U+0301: дообучение LoRA на аудиокнигах и фразах с намеренно сдвинутыми ударениями. Исходная Qwen следует знаку на ненормативном слоге в 21% случаев, после дообучения — в 49%, голос и разборчивость те же. Абзац с омографами и пары «до / после» — на отдельной странице, решение — ADR 0023.

Раунд 14–15. Словарь, который собирает себя сам

«Никакой ручной работы я делать не хочу, мне важно собрать автоматизированный инструмент в конечном итоге без ручного участия».

Предыдущий раунд собрал словарь из 79 терминов и озвучил его пачками для проверки на слух — по десять терминов в клипе, 93 секунды на весь просмотр. Этот подход отменяется целиком: любая схема, где решение выносит человек, не масштабируется на пополнение корпуса.

Значит нужна автоматическая проверка произношения — включая ударение, где я уже дважды провалился.

Третья попытка с ударением

Оба провалившихся детектора искали ударный слог внутри сигнала по энергии. Прежде чем винить метод, проверил эталон: Silero действительно уважает метки — звук меняется от переноса ударения. Разметка была верной, провалился подход.

Сработало, когда задача перевернулась: ничего не сегментировать, а спрашивать, на какую из известных записей похожа неизвестная. Silero умеет ставить ударение в заданный слог — значит можно нарезать эталоны для всех позиций, а кандидата сравнивать по форме контура громкости, нормированной так, что тембр и темп сокращаются.

Три подхода к определению ударения
точность восстановления позиции
Валидация межголосовая: эталоны от одного голоса Silero, проверка на другом — та же рассогласованность, что будет между Silero и Qwen.

И тут же четвёртая ошибка измерителя

Первый полный прогон: 30 принято, 12 «исправлено», 37 не решено. Исправления — растяжки вроде «ээс-ку-эль». Посмотрел, что услышала распознавалка у непрошедших:

ТерминПисалиУслышаносходство
JVM«джей-ви-эм»«что такое JVM.»0.0
HTTP«эйч-ти-ти-пи»«что такое HTTP.»0.0
Spring Boot«Спринг Бут»«что такое Spring Boot.»0.0

Модель произнесла всё правильно. ASR узнал термин и записал его своим письмом — латиницей. А сравнение шло кириллицы с латиницей и давало ноль. Инструмент браковал верные произношения и подменял их мусором.

Починка: засчитывать совпадение и с английским исходником, разворачивать цифры («3Map» → «триMap»), сводить оба алфавита к общей фонетической записи, где «Спринг Бут» и Spring Boot дают одну строку. Проверено без повторной генерации, на уже собранных транскриптах: проходящих стало 78 из 79 вместо 30.

Пятая ошибка: я сам

JWT я написал как «джей-ви-ти». Правильно — «джей-дабл-ю-ти». Инструмент эту опечатку подтвердил, потому что проверяет соответствие звука написанию, а не написания термину.

Вывод: аббревиатуры нельзя писать руками. Побуквенное чтение выводится из самого термина правилом, руками остаётся только список идиоматических исключений — тех, что давно стали словами: джейсон, рест, эс-ку-эль, си-джи-либ. Это константа языка, а не работа по корпусу. Правило исправило JWT, AOP и URL, и все три теперь распознаются со сходством 1.00.

79
терминов в словаре
подтверждены автоматически
64
со сходством 1.00
распознаны точно
12 с
генерации на термин
человек в цикле не участвует

Как это звучит

Слева — то, что попало бы в озвучку: мои опечатки, заверенные сломанной проверкой, и её же «починки» растянутой гласной. Справа — то, что выбрал исправленный конвейер.

JWT · было«джей-ви-ти» — W прочитан как «ви»
JWT · стало«джей-дабл-ю-ти» — выведено правилом · 1.00
AOP · было«а-о-пи» — A прочитан как «а»
AOP · стало«эй-о-пи» · 1.00
URL · было«юар-эль» — слипшееся начало
URL · стало«ю-эр-эль» · 1.00
SQL · «починка» сломанного измерителя«ээс-ку-эль» — растянутая гласная
SQL · стало«эс-ку-эль» — идиоматическое чтение · 1.00
CountDownLatch · «починка»«Кааунт Даун Лэтч»
CountDownLatch · стало«Каунт Даун Лэтч» — исходное написание вернулось

Последняя пара — самая показательная. Написание было верным с самого начала; сломанный измеритель объявил его негодным и «исправил», растянув гласную. После починки проверки конвейер вернулся к исходному варианту сам.

Чего автоматика не решает. Конвейер добивается того ударения, которое считает верным RUAccent. Где акцентуатор ошибается на жаргоне — на «дженерики» он даёт второй слог, а в речи разработчиков слышен первый — инструмент этого знать не может. Это вопрос нормы, а не измерения.

Что помогло и что не помогло

Помогло

  • Round-trip через ASR как метрика. Превратил спор о впечатлениях в число и заодно стал приёмкой в сборке.
  • Переписывание текста в объяснение. Дало больше, чем любая смена движка.
  • Подбор человеческого референса замером. Восьмикратный разброс между чтецами.
  • Модель, дообученная на разборчивость (RL-V2 вместо PODCASTER) — ровно вдвое.
  • Ритм через типизированные реплики. Улучшил и восприятие, и метрику.
  • Затухание и фон комнаты в паузах. Разрыв сигнала в 5.6 раза меньше.
  • Отбор референса по трём измерениям сразу — темп, плотность собственных пауз, разборчивость. Дал 102 слов/мин и CER 1.0% без единой обработки.
  • Нарезка референса по границам фраз. Модель получает его транскрипт дословно; чистая нарезка того же клипа дала 7.3% → 1.0%.
  • Весь текст без внутренней пунктуации, короткими предложениями. Запятая читается моделью как повод остановиться, а придаточное заставляет ухо удерживать начало фразы.
  • Выбор отрывка внутри выбранного голоса. Тембр принадлежит чтецу, манера — отрывку; у одного чтеца плотность пауз гуляет втрое.
  • Прямая проба на контрольной фразе. Косвенные показатели клипа её не предсказывают.
  • Переписывание слов с неоднозначным ударением. Единственный стопроцентно надёжный способ управлять ударением в модели, которая не понимает разметки.
  • Проверка измерителя на контрольном материале. Спасла от публикации ошибочных 61%, а позже — от словаря, собранного из мусора.
  • Определение ударения сопоставлением с эталоном вместо поиска ударного слога в сигнале: 77% против 25% у случайного.
  • Вывод написания аббревиатур правилом. Убрал целый класс моих опечаток.
  • Пересмотр обзора движков. Смена архитектуры дала больше, чем десять раундов настройки выбранной: 38 пауз вместо 120 и 1.8% тишины вместо 20%.
  • Короткое затухание, 8 мс. Достаточно против щелчка, слишком мало, чтобы задеть фонему.
  • Разбиение на реплики по смыслу — помогало, пока референс был плохим. С хорошим референсом преимущество исчезло, и сплошной синтез оказался слитнее.

Не помогло

  • Ручка speed у диффузионной модели. Роняет разборчивость впятеро. Главный отрицательный результат.
  • Больше шагов диффузии. nfe 64 против 32 — эффекта нет, время вдвое.
  • Чёткий движок как донор артикуляции. Копируется манера, а не разборчивость.
  • Транслитерация нотации. Делает текст произносимым, но не воспринимаемым.
  • Выбор движка «посвежее». Silero v4 объективно хуже v3.1.
  • Проверка длительности как приёмка. Ловит беззвучные выбросы, слепа к съеденным слогам. Вытеснена CER.
  • Разметка ударений знаками. Плюс произносится вслух, диакритика коверкает слово, апостроф и дефис ломаются на длинных словах.
  • Автоматический детектор ударений по энергии. Две попытки: 70% ошибок на живой речи и 16% верных при 25% у случайного.
  • Сравнение строк без учёта алфавита. ASR пишет узнанный термин латиницей; проверка браковала верные произношения и «чинила» их в мусор.
  • Борьба с рваностью настройками. Четыре раунда правили паузы, затухания, отрывки и пунктуацию — а свойство принадлежало архитектуре движка.
  • Доверие к внешнему рейтингу без проверки даты. Лидерборд, на который опирался отбор, не обновлялся год.
  • Растяжение как основной способ задать темп. Работает лучше ручки модели, но лечит следствие: темп принадлежит референсу.
  • Вставленные паузы целиком. Механизм прожил три итерации — резкие, длинные, одинаковые — и был отвергнут. Симптом менялся, источник был один.
  • Обрезка тишины по порогу энергии. Глухие щелевые лежат ниже любого разумного порога — обрезка съедала ровно те согласные, ради которых затевалась.
  • Разброс темпа между репликами. Влияние на метрику непоследовательно, в пределах шума. Недоказанное усложнение убрано.
  • Глобальный множитель пауз. Дал 34% тишины и паузы по 9 секунд. Тишина должна задаваться осознанно в одном месте, а не получаться произведением четырёх независимых множителей.

Что осталось нерешённым

ESpeech даже вылеченный не догоняет Vosk и Silero по чистоте — 5–7% против 1–3%. Это осознанная плата: диффузионная модель звучит естественнее и рискует сильнее.

Сама метрика тоже небезупречна. Whisper плохо разбирает транслитерированные термины, поэтому образец про дженерики показывает завышенный CER у всех движков одинаково. Метрика достоверна для сравнения вариантов на одном тексте и обманчива между разными.

Приложение: все замеры

Сырые json — в каталоге results/, скрипты — в scripts/, по одному описанию на итерацию — в experiments/.


Всё локально: ESpeech-TTS-1 (Apache-2.0), Piper (MIT), Vosk-TTS (Apache-2.0), faster-whisper (MIT), референсы — общественное достояние (LibriVox). У Silero бесплатная лицензия ограничена образовательными целями.