Хабрахабр

Рисуем речь: Software Automatic Mouth

Прошлогоднюю статью «Рисуем звук» я завершил признанием: «А можно ли нарисовать звук с чистого листа, не обводя спектрограмму аудиозаписи? Скажу честно, у меня не полу­чилось.» Но недавно я узнал про S.A.M. — выпущенный в 1982 г. компанией Don’t Ask Software, он стал первой коммерчески успешной программой для синтеза речи на ПК. В середине 2000-х немецкие демосценщики Tobias Korbmacher и Sebastian Macke взяли ассемблерный листинг S.A.M. для Commodore 64 и сконвертировали его в нечитаемый, но работоспособный код на Си; затем в 2014 г. британец Vidar Hokstad постарался привести код на Си в читаемый вид — вручную давая переменным осмыс­ленные названия и заменяя goto на циклы и ветвления; и наконец, в 2017 г. ещё один немец Christian Schiffler переписал код с Си на JavaScript. Испробовать его в действии как «чёрный ящик» можно на discordier.github.io/sam.

По-моему, примитивный синтезатор речи на JavaScript — самый удобный подопытный образец для тех, кто хочет разобраться, как в целом работает синтез речи. Мой форк S.A.M. с существенно почищенным кодом и комментариями доступен на github.com/tyomitch/sam. К сожалению, у предыдущих авторов интерес к S.A.M. успел угаснуть, и им сейчас не до разбора пулл-реквестов в хобби-проект многолетней давности.

S.A.M. состоит из четырёх функциональных компонентов:

  1. Reciter переводит текст на английском в фонемную запись: например, «A LITTLE TOO LOW» (пример из приложенной к S.A.M. демо-программы) превращается в «AH LIHTUL TUW5 LOW».
  2. Parser превращает фонемную запись в фонетическую: из «AH LIHTUL TUW5 LOW» получается "AH, ,L,IH,DX,AX,LX, ,T,*,*,UX,WX, ,L,OW,WX". Для каждого выводимого фона Parser задаёт также длительность и тон.
  3. Renderer строит по фонетической записи массив частот, амплитуд и прочих акустических характеристик;
  4. Последний, безымянный компонент (функция ProcessFrames) превращает массив частот и амплитуд в PCM-поток для аудиовывода.

В этой статье я разберу все четыре компонента по очереди.

Reciter

Reciter прилагался к S.A.M. как отдельная программа: создатели заявляли, что заложенные в Reciter 469 правил произношения позволяют верно транскрибировать порядка 90% английских слов. Это значит, что транскрипция каждого десятого слова нуждалась в ручной правке перед тем, как подать её на вход следующим компонентам.

В S.A.M. используется собственная система транскрипции, где английские фонемы обозначаются отдельными символами из набора [A-Z/] или парами по два таких символа:

Кроме фонем, в транскрипции S.A.M. используются цифры 1–8 для обозначения ударения и тона: 1 обозначает «очень эмоциональное» ударение, 4 — обычное ударение, 6 — нейтральный тон, 8 — «крайнее падение тона».

Устроен Reciter достаточно просто: ко входной строке поочерёдно применяются контекстно-зависимые правила из списка, например правило "(IR)#=AYR" заменяет текст ⟨ir⟩ перед гласной на /aɪr/; правило ".(S) =Z" заменяет ⟨s⟩ между звонкой согласной и пробелом (концом слова) на /z/; правило "(U)^^=AH5" заменяет ⟨u⟩ перед двумя согласными подряд на /ʌ/, и делает слог ударным. Важно отметить, что во многих словах Reciter не отмечает ударение ни на одной гласной, а в некоторых — отмечает сразу на нескольких гласных: например, слово ⟨provoking⟩ превращается в "PRUW4VOW5KIHNX", т.е. /ˈpruˈvoʊkɪŋ/. Внимательный читатель заметит, что лишнее ударение — не единственная ошибка в этой транскрипции.

Я решил, что транскрипция — это наиме­нее интересная часть синтезатора речи; и учитывая относительно низкое качество транскрипции на выходе Reciter, я решил им не пользоваться вовсе. Есть несколько свободно доступных интернет-сервисов для транскрипции отрывков английских текстов; вместо эвристических правил эти сервисы используют достаточно большие словари. По моему опыту, наилучшее качество транскрипции — у сервисов tophonetics.com и photransedit.com; при этом у второго есть ряд недостатков: он использует не вполне стандартные обозначения фонем, отмечает ударение даже в односложных словах, и что самое неудобное — он написан на ASP.NET и требует в POST-запросах правильных значений __VIEWSTATE и __EVENTVALIDATION, что усложняет его использование со сторонних сайтов. Поэтому в моей демонстрации устройства и работы S.A.M., доступной на tyomitch.github.io, я задействовал транслитерацию через https://cors-anywhere.herokuapp.com/https://tophonetics.com/

Parser

В отличие от Reciter, названного так самими создателями S.A.M., компонентам Parser и Renderer названия дали немецкие реверс-инженеры, так что эти названия не вполне точно отражают назначение этих компонентов.

У Parser три основные задачи:

  1. Разбиение «сложных» фонем (аффрикат, дифтонгов) на составляющие. В эту же категорию попадают взрывные согласные (разным этапам их произношения соответствуют акустически разные звуки) и «псевдо-фонемы» UL, UM, UN, обозначающие слоговые согласные [l̩, m̩, n̩]. Вообще говоря, в фонемной записи их принято обозначать как /əl, əm, ən/; но Parser позволяет использовать для них и сокращённые обозначения, которые разворачиваются в AXL, AXM, AXN соответственно.
  2. Выбор аллофонов, т.е. вариантов произношения одной и той же фонемы. В приведённом выше примере «AH LIHTUL TUW LOW» вы могли заметить, что фонема /t/ превратилась в [ɾ] (DX) между гласными и в [t] (T,*,*) в начале слова. (Звёздочки обозначают безымянные фоны.) Кроме того, /l/ превратилась в [ɫ] (LX) на конце слова, и в [l] (L) перед гласной.
  3. Задание длительности и тона для каждого фона.

S.A.M. поддерживает 81 фон, из которых 61 имеют названия и могут использоваться в фонемной записи, чтобы «перехитрить» Parser и задать сразу нужный звук. Остальные 20 фонов — безымянные; из них 18 могут появиться только в результате работы Parser, а фоны с кодами 46 и 47 не могут появиться никак, и вероятно, остались недоудалёнными по недосмотру разработчиков S.A.M.

Фоны с кодами 0–4 ( .?,-) соответствуют тишине; остальные же сведены в следующую таблицу:

Действия, выполняемые Parser, состоят из семи этапов:

  1. Собственно парсинг: по входной строке формируется список кодов фонов и параллельный ему список тонов, заданных цифрами во входной строке.
  2. Применение к списку фонов набора из двух десятков правил: например, замены /t/+/r/ → [tʃ]+[ɹ̠] и /k/+/не-передняя гласная/ → [k]+[гласный]. (/k/ перед передними гласными остаются неизменными и соответствуют фону [kʲ].)
  3. CopyStress: тон, заданный для ударных гласных, распространяется на предшествующие им согласные.
  4. SetPhonemeLength: для каждого фона подставляется длительность (в условных «фреймах»). Используются две таблицы долготы фонов — одна для ударных слогов, другая для безударных.
  5. AdjustLengths: применение набора из семи правил для коррекции длительностей фонов. Например, гласные перед звонкими согласными удлиняются в полтора раза, а подряд идущие взрывные согласные сокращаются вдвое.
  6. ProlongPlosiveStopConsonants: взрывные согласные перед гласными, плавными и фрикативными согласными разбиваются на тройки фонов. Первый фон в тройке соответствует меньшей интенсивности звука, второй — полной интенсивности, третий — тишине.
  7. InsertBreath: фраза разбивается по «немым» фонам ( .?,-) на «выдохи» длительностью до 232 фреймов (это порядка 2½ секунд). В реализации S.A.M. для ретро-ПК такое разбиение было необходимо для экономии памяти; в версии на JavaScript оно лишено всякого смысла, и в моём форке оно удалено.

На выходе Parser получаются три параллельных списка: коды фонов, их тоны, и их длительности.

Renderer

Этот компонент отвечает за синтез речи в узком смысле слова. На входе он принимает список фонов с заданными тонами и длительностями, а также параметры, влияющие на синтезируемый голос. На выходе он производит восемь параллельных списков: частоты формант F1–F3, их интенсивности (амплитуды), основная частота F0 (тон голоса), и значения sampledConsonant, о которых ниже будет рассказано подробнее.

Со ссылкой на инструкцию S.A.M. приводятся следующие примеры значений параметров голоса:

Стоит отметить, что параметр Speed используется не в Renderer, а уже на этапе генерации аудио: от этого параметра зависит длительность звука, генерируемого для одного фрейма. Кроме параметра Speed, длительность фрейма зависит и от типа звука, как будет объяснено ниже.

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

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

Эти «всплески» невозможно получить чистым формантным синтезом, поэтому звуки шумных согласных S.A.M. воспроизводит из таблицы сэмплов. Упомянутые выше значения sampledConsonant выбирают часть таблицы, соответствующую конкретному шумному согласному.

Действия, выполняемые Renderer, состоят из пяти этапов:

  1. SetMouthThroat: для гласных и сонорных фонов (коды 5–29 и 48–53) табличные значения частот F1 и F2 домножаются на параметры Mouth и Throat соответственно.
  2. CreateFrames: для каждого фона записывается соответствующее его длительности число значений в восемь названных параллельных списков. Сами значения берутся из таблиц по коду фона, при этом тоны переводятся из условной шкалы (1–8) в величину прибавки к параметру Pitch (1 → −32, 6 → 0, 8 → +12). Кроме того, точки и вопросительные знаки превращаются в плавное (на протяжении 30 фреймов) понижение или повышение тона, соответственно.
  3. CreateTransitions: значения частот F0–F3 и амплитуд F1–F3 на границе между соседними фонами линейно интерполируются. Ширина границы, внутри которой выполняется интерполяция, зависит от кодов обоих фонов.
  4. Частота F0 заменяется средним значением между ней и F1, чтобы создать «pitch contour», с которым синтезированная речь будет звучать не так монотонно.
  5. Наконец, значения амплитуд после интерполяции переводятся из логарифмической шкалы (децибелы) в линейную, используемую в стандартном PCM.

Генерация аудио

С физической точки зрения, речь — это создаваемый голосовыми связками glottal pulse train (см. рис.), который по пути наружу проходит через полости рта и носа (речевой тракт), и те, как резонатор, усиливают в гортанной волне определённые гармоники. Частота гортанной волны — это и есть основная частота голоса F0. Как правило, её значения заключены между 100 и 400 Гц: у мужчин ниже, у женщин выше, у детей ещё выше.

Модель голоса, используемая в формантном синтезе, состоит в том, что к гортанной волне применяется несколько полосовых фильтров, каждый из которых выделяет одну форманту. Ширина выделяемой полосы зависит от частоты форманты, и по экспериментальным данным, составляет до 200 Гц:

В моей демонстрации S.A.M. на tyomitch.github.io используется именно такой подход: при значении параметра по умолчанию Bandwidth=3 каждая форманта привносит в получающийся аудиосигнал гармоники F0 в пределах ±5.9% частоты форманты. Это примерно соответствует вышеприведённым графикам: форманта частотой 3 КГц выделяет полосу шириной 177 Гц. В классической же реализации S.A.M. к генерации нужного числа гармоник подошли более изобретательно: для каждой форманты генерируется одна волна, но фаза этой волны обнуляется с частотой F0. В моей демонстрации можно перейти к режиму, синтезирующему по одной волне для каждой форманты (но без обнуления фазы), сбросив флажок у параметра Pitch.

Функция ProcessFrames в классическом S.A.M. обрабатывает глухие и звонкие шумные согласные отдельно от всех остальных фонов:

  • Для глухих шумных согласных воспроизводится сэмпл из таблицы. Длительность сэмпла предопределена, и не зависит от параметра Speed. Самый длинный сэмпл ([s]) длится 105 мс, самые короткие ([p] и [t]) — по 10.4 мс.
  • Для гласных и сонорных согласных синтезируются $\frac{Speed\cdot162}{50}$ PCM-сэмплов, состоящих из суммы трёх волн: гармонических (синусоидальных) для F1 и F2, и меандра (прямоугольной волны) для F3. Таким образом, один фрейм при значении параметра по умолчанию Speed=72 соответствует 10.6 мс гласного звука.
  • Для звонких шумных согласных сначала по формантам, как для гласных, синтезируются $\frac34\cdot\frac{Speed\cdot162}{50}$ PCM-сэмплов, а затем воспроизводится сэмпл из таблицы. Длительность сэмпла зависит от тона звука, но от параметра Speed не зависит. В безударном слоге, при значении параметра по умолчанию Pitch=64, один фрейм соответствует 1.6 мс воспроизведения сэмпла, т.е. 9.5 мс в сумме для звонкого шумного согласного.

Для шумных согласных используются пять таблиц сэмплов: одна для альвеолярных ([t, s, z]), одна для палато-альвеолярных ([ʃ, ʒ]), одна для губных и зубных ([p, f, v, θ, ð]), и по одной для [ç] и [h]. Сэмплы, относящиеся к одной таблице, отличаются друг от друга только длительностью и интенсивностью.

В моей демонстрации, ради упрощения, для всех фреймов генерируется равный по длительности звук, и эта длительность зависит только от параметра Speed: при его значении по умолчанию один фрейм соответствует 10.4 мс звука. Как показывают эксперименты, это «в среднем» соответствует классическому S.A.M., хотя относительно него отдельные звуки в синтезированной фразе могут «съезжать» на единицы мс вперёд или назад.

Напоследок продемонстрирую три спектрограммы приветственной фразы, созданные классическим аудиогенератором S.A.M. и моим аудиогенератором со включённым и с выключенным синтезом тона:

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

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»