Speech API 4. Работа с голосом

Эта статья об управлении параметрами чтения чтекстов в Delphi функциями SpeechAPI.

Надеюсь, что вы прочитали первую часть SpeechAPI в Delphi и статью MSAgent и SpeechAPI, поэтому перейду непосредственно к способу управления параметрами чтения. Я опишу общий способ управления, а затем рассмотрим разницу управления параметрами речи при чтении методом Speak у MSAgent и чтением функциями API напрямую.

Теги

Как пишут в буржуйской офицальной документации к Microsoft Speech API: SAPI поддерживает изменения речевого вывода через специальные теги,вставляемые в читаемую текстовую строку. Эти теги помогают изменять параметры голосового движка для улучшения трансляции текста в речь. Поддерживает теги не только речевые движки, но и MSAgent. Теги — это основной способ управления голосовыми возможностями речевого синтезатора. Например: \spd=100\ — этот тег изменяет скорость речи на значение равное 100. Вот некоторые правила синтаксиса для речевых тегов:

  • Все теги начинаются и заканчиваются символом наклонной черты влево (\).
  • Отдельный бэкслэш не допускается в пределах тэга. Чтобы включить бэкслэш в текстовый параметр тэга, используйте двойную наклонную черту влево (\\).
  • Теги воспринимаются без учета регистра. Например, \Spd=100\ — тот же самый что и \SPD=100\.
  • Теги пробеловосприимчивы, то есть не допускают в себе лишних пробелов. Например, \Rst\ — не тот же самый что и \ Rst \

MSAgent поддерживает следующие теги: Chr, Ctx, Emp, Lst, Map, Mrk, Pau, Pit, Rst, Spd, Vol. При чтении через SAPI этот набор немного шире. Теги создавались для корректировки преобразования текста в речь, но некоторые из них изменяют стиль голоса, например на шепот. Поэтому одни из них имеют параметры, а у других они отсутствуют, как у тега \Emp\. А теперь подробнее про
каждый из них!

Тег Синтаксис Описание
Chr \Chr=string\ Устанавливает тип голоса:

  • «Normal» (Default) — нормальный голос.
  • «Monotone» — монотонный.
  • «Whisper» — шепот

Кроме того, допустимые значения этого тега могут меняться у разных движков, то есть одни движки способны поддерживать какие-то значения, а другие не поддерживать часть из них.

Ctx \Ctx=string\ Устанавливает тип содержимого читаемой строки

  • «Address» — Адрес и/или телефонный номер
  • «E-mail» — мыло
  • «Unknown» — неопределенный тип

Значение этого тега также может варьироваться в различных речевых движках

Emp \Emp\ Подчеркнуть выражение следующего слова. Выделяемое речью слово должно следовать сразу за тегом.

Этот тег тоже поддерживается Text-To-Speech генератором, поэтому данные параметры могут существовать у различных речевых синтезаторов.

Lst \Lst\ Повторить последнее сказанное выражение

*Неподдерживается при чтении функциями API, то есть повторяется последняя
фраза при чтении методом Speak у персонажа.

Map \Mapspokentext«=«balloontext«\ Тег указывает как должен отображаться читаемый текст в воздушном шарике. Это удобно, когда нужно прочитать один текст, а показать вместо него другой. Этот тег
я активно использую в программке LittleHelper.
Он поддерживает свой собственный словарь произношений, если нужно читать
слово «юзер» как «йууузер», то приходится пользоваться
этим тегом, чтобы слово в воздушном шарике(в баллоне) выглядело в нормальном
виде.
*Естественно, что этот тэг тоже поддерживается при чтении MSAgent, а не функциями API.
Mrk \Mrk=number\ Тег определяет в тексте закладку. Затем при чтении генерируется событие, которое можно обработать. Рассмотрим это позже.

*Число должно быть целочисленное, больше 0, и не равняться 2147483647 или 2147483646.

Pau \Pau=number\ Пауза в речи на указанное количество милисекунд.(1сек = 1000милисекунд)
Pit \Pit=number\ Указывает тон речи в герцах.

*Диапазон значений зависит от речевого синтезатора.(Проблема будет рассмотрена ниже)

Rst \Rst\ Сбрасывает все теги на значения по умолчанию
Spd \Spd=number\ Скорость речи. Количество слов в минуту.

*Диапазон значений зависит от речевого синтезатора.(Проблема будет рассмотрена ниже)

Vol \Vol=number\ Громкость речи. Диапазон значений от 0 до 65535.

В случае с MSAgent…

Ну, а как, скажете вы мне, это дело использовать? Очень просто. Если вы читаете
текст с помощью MSAgent, то есть методом Speak, то все теги необходимо вставить
в читаемую строку. Например:

… Chars.Speak(‘\spd=60\Привет! Привет! Привет! \spd=120\Как дела? Расскажи скорее!’, »); …Это все! Кроме того, теги не будут отображаться в воздушном шарике. Этот
способ единственный, кроме того, он очень оригинальный и простой для конечных
разработчиков и пользователей. Ведь даже пользователь сможет вставить в читаемую
строку нужный тег. Еще хотелось бы добавить пару слов про закладки(bookmark).
По достижению тега закладки \Mrk=number\ в тексте при чтении
у нашего ActiveX-компонента генерируется событие OnBookmark. Пропишите все,
что вам нужно в этот обработчик и пользуйтесь. Для демонстрации тегов я написал
следущий пример:

AgentTags

Программка является продолжением проекта AgentRead и называется AgentReadTags.
Скачивайте его в конце страницы.

В случае с SAPI…

Теперь попытаемся усовершенствовать проект SpeechTest — добавим в него функции
изменения тона речи и т.д.! Назовем поэтому его SpeechTegs(ссылка в конце
странице). Продолжим! Если вы пытаетесь читать текст функциями Speech API,
т. е. без персонажа, например как мы делали в первой
статье
, то можно рассмотреть два случая. В одном из них параметры задаются
динамически прямо в тексте, в другов — теги внедряются специальными функциями.
Когда вы желаете прочитать текст функциями SAPI, следует обратить внимание
на включение флага dwFlags
в процедуре запуска чтения, так как в положении 1 он включает обработку речевых
тегов. Таким образом, конец процедуры чтения SpeakClick из проекта SpeechTest
статьи SpeechAPI в Delphi примет вид:

try
//Обратите внимание на dwFlags! Он равен 1. При значении 0 не будут обрабатываться теги!
fITTSCentral.TextData(CHARSET_TEXT, 1, SData, nil, IID_ITTSBufNotifySink);
except
end;

Теперь можно в читаемый текст вставлять любые теги и они также будут обрабатываться TTS-генератором.

Еще хотелось бы отметить, что при чтении текста функциями API доступны еще некоторые теги. Это естественно,
так как при чтении методом Speak у объекта ActiveX MSAgent мы не используем напрямую функций API, а только сообщаем компоненту, что нужно сделать. В некотором смысле это является облегчением для конечных разработчиков и в тоже время
ограничивает функциональность речевых тегов. Хотя при чтении текста различными движками через персонаж используемые функции необходимы всего лишь для определения TTSModeID(см. статью MSAgent и SAPI), то есть для нахождения CLSID синтезатора. Вот некоторые из наиболее интересных тегов:

\Dem\ — Антивыделение слова. Тег обратный тегу \Emp\. Так же как и в случае с командой Emp, слово, которое не должно выделяться речью, обязано следовать непосредственно за тегом.

\Eng=value\ — Дает небходимую команду напрямую речевому движку. Поддерживаемые команды должны быть описаны в документации к синтезаторам речи.

\Pro=boolean\ — при значении 1 соблюдаются правила интонации. При значении 0 голос приобретает монотонный оттенок

\RmS=boolean\ — при значении 1 читает слова по буквам. При значении 0 нормальными словами.

Кроме того, нередко возникает ситуация, когда нужно изменить такие атрибуты голоса как скорость чтения, высота и т.д. во время чтения! Что делать, если текст уже читается? Не останавливать же его для того, чтобы вставить тег в
текст, и запускать на чтение заново? В таких ситуациях следует воспользоваться специальными функциями. Это и есть второй способ вставки тегов при чтении функциями SpeechAPI. У главного интерфейса TTS-генератора ITTSCentral
есть метод:

function Inject(pszTag: PAnsiChar): HResult; stdcall;

через который все теги передаются речевому синтезатору. Удобно, что этой функцией можно пользоваться как между чтением текста, так и во время чтения. Если вы выполнили данную функцию во время чтения, то результат вы услышите только, когда TTS-генератор закончит читать текущий буфер и начнет следующий. Так случается, если читаемый текст большой, и вы в середине решите изменить скорость речи или высоту голоса, тогда вы услышите изменение в следующием предложении.

Итак пример использования функции. Представим ситуацию: существует на форме компонент TEdit с именем edSpeed и кнопка TButton с именем Button1, вам хотелось бы изменять скорость речи в соответствии с числом введеным в поле редактирвоание. Обработка события щелчка по кнопке примет вид:

procedure TForm1.Button1Click(Sender: TObject);
begin
try
OleCheck(fITTSCentral.Inject(PChar(‘\Spd=’+edSpeed.Text+‘\’)));
except
end;
end;

Я намеренно повсеместно использую конструкцию tryexcept, так как это хорошая привычка(если вы, конечно, не отлаживаете приложение в целях найти ошибки), потому что бывает такие ситуацие, когда некоторые движки не поддерживают отдельные теги. В основном, это спицифические теги, поэтому в демонстрационных примерах я поступаю именно так.

Атрибуты голоса

Все бы хорошо, и на этом можно бы было закончить! Однако, как вы сами уже представляете, разные фирмы наградили свои речевый синтезаторы различными спосбностями, такими как свои диапазоны скорости речи, высоты голоса. Естественно, что не хочется подгадывать рабочие диапазоны «на глаз». Для этого существует интерфейс ITTSAttributes. Через него-то и можно узнать какие пределы существуют в речевых способностях движка.

Добавлю еще пару штрихов. Я вывел в отдельную часть информацию об атрибутах голоса, так как многие параметры синтезатора, регулируемые через теги не называются атрибутами(хотя скорость, высоту и громкость можно изменять включением тегов).

Атрибутами называются те параметры, которые регулируются интерфейсом ITTSAttributes. Это скорость(Speed), высота(Pitch) и громкость(Volume).

Продолжим совершенствовать проект SpeechTest, поэтому назовем его SpeechTegs (готовый проект в конце страницы). Для работы с атрибутами объявим переменную fITTSAttributes типа соответствующего интерфейса, а затем в процедуре изменения движка при выборе его названия в КомбоБоксе, добавим строчку получения интерфейса
ITTSAttributes
.

… OleCheck(fITTSCentral.QueryInterface(IID_ITTSAttributes, fITTSAttributes)); …

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

Сразу приведу пример, как можно узнать, нижний диапазон скорости чтения речевого синтезатора, и сразу все станет понятно!

…. //Заставляем движок изменить скорость на min

fITTSAttributes.SpeedSet(TTSATTR_MINSPEED);
//Читаем это значение в переменную MinSpeedVoice
fITTSAttributes.SpeedGet(MinSpeedVoice); ….

Дело в том, что изменяется скорость системными значениями от 0 до 4294967295($ffffffff в DWORD), а нам все-таки привычнее скорость измерять слов/мин. Да и для изменения атрибутов через теги нужно использовать значения в таком формате. Поэтому мы сначала меняем скорость методом SpeedSet на минимальную(кстати, константа TTSATTR_MINSPEED = 0), а затем читаем нормальное значение процедурой SpeedGet в переменную MinSpeedVoice
типа DWORD. Аналогично узнаются все остальные атрибуты, для этого есть соответствующие константы в модуле speech.pas, я не буду их комментировать, их названия говорят сами за себя:

TTSATTR_MINPITCH = 0;
TTSATTR_MAXPITCH = $ffff;

TTSATTR_MINSPEED = 0;
TTSATTR_MAXSPEED = DWORD($ffffffff);

TTSATTR_MINVOLUME = 0;
TTSATTR_MAXVOLUME = DWORD($ffffffff);

Пример использования атрибутов также работает в проекте SpeechTegs.

Однако, не все так просто, перед тем как регулировать скорость или еще какие-нибудь атрибуты, а также получать ссылку на интерфейс ITTSAttributes, необходимо выполнить проверку на предмет поддержки речевым синтезатором атрибутов вообще, и регулирования отдельного атрибута(который вы желаете изменить) в частности. Потому как, мало ли какой нам движок попадется? Эту операцию нам поможет провернуть переменная fpModeInfo типа PTTSModeInfo, которая хранит параметры движка(конечно, если вы их сохраняете в этой переменной). В демопроекте я именно так и поступаю — записываю параметры в эту переменную при выборе синтезатора из списка. Этот указатель на интерфейс(а PTTSModeInfo так и описан как PTTSModeInfo = PTTSModeInfoA, а PTTSModeInfoA = ^TTTSModeInfoA) имеет интересное поле — dwInterfaces: DWORD. Каждый бит этого поля может рассказать нам о том, какие интерфейсы поддерживает тот или иной движок. Если бит включен, то интерфейс поддерживается. Естественно, добраться до каждого бита можно побитовым умножением. Например, абстрактная ситуация: вам дано число 0111:0101, и нужно проверить включен или выключен 3-ий бит. Что будем делать? Умножим данное число на контрольное число 0000:0100(иначе говоря на четверку), и если получим отличное от 0 число, то 3 бит включен. Так как полученное число будет равно контрольному — 0000:0100.

Хорошо, все понятно! Но нужно знать какой бит за что отвечает. Вот их описания:

Интерфейс
Константа
Значение
Номер
бита
ILEXPRONOUNCE TTSI_ILEXPRONOUNCE $00000001 0
ITTSATTRIBUTES TTSI_ITTSATTRIBUTES $00000002 1
ITTSCENTRAL TTSI_ITTSCENTRAL $00000004 2
ITTSDIALOGS TTSI_ITTSDIALOGS $00000008 3
ATTRIBUTES TTSI_ATTRIBUTES $00000010 4
IATTRIBUTES TTSI_IATTRIBUTES $00000010 4
ILEXPRONOUNCE2 TTSI_ILEXPRONOUNCE2 $00000020 5

Теперь, если выполняется такое условие:

if (TTSI_ITTSATTRIBUTES and fpModeInfo^.dwInterfaces) <> 0 then
begin

end;

то вместо троеточий можно вставлять код, работающий с интерфейсом атрибутов, его получение черезQueryInterface. Но это еще не все! Теперь нужно проверить, какие именно особенности поддерживаются
движком. Для этого, как вы наверное догадались, тоже существует какое-то поле у типа PTTSModeInfo, которое может по битам рассказать, что движок поддерживает, а что нет. Так вот имя этого поля dwFeatures. Features так и переводится как «особенности». Приведу только названия некоторых констант, так как все их можно увидеть в модуле speech.pas, а из названия констант и так понятно, что ими можно проверить:

TTSFEATURE_VOLUME = $00000002 ; //{bit 1}

TTSFEATURE_SPEED = $00000004 ; //{bit 2}

TTSFEATURE_PITCH = $00000008 ; //{bit 3}С дополнительной проверкой код изменения скорости речи примет вид:

if (TTSI_ITTSATTRIBUTES and fpModeInfo^.dwInterfaces) <> 0 then
begin
try
//Получаем интерфейс управления атрибутами чтения
OleCheck(fITTSCentral.QueryInterface(IID_ITTSAttributes, fITTSAttributes)); //Проверяем поддержку изменения скорости побитовым умножением if (TTSFEATURE_SPEED and fpModeInfo.dwFeatures) <> 0 then beginend; except end; end;

Ну вот теперь можно работать с атрибутами движка: узнавать диапазоны его возможностей, изменять параметры и т.д. Однако в рабочих проектах я еще вставлял для полной уверенности проверку на получение и изменение атрибутов. То есть реально можно ли прочитать значение, к примеру, скорости и изменить ее! Эта операция тоже достаточно проста, стоит всего лишь проверить насколько успешно выполняются эти операции. Для этого нужно сравнить HResult возращаемый системой при работе с функциями Что-тоGet и Что-тоSet(в случае со скоростью SpeedSet и SpeedGet) с константой TTSERR_NOTSUPPORTED. Если значение, возвращаемое функциями равно этой константе, то при выполнении их возникла ошибка, следовательно работать с этим атрибутом нам не стоит.

Диалоги с речевыми движками

Кроме чтения речи, изменения атрибутов движки поддерживают(если этим их наградили разработчики) стандартные диалоги. Это диалоги: About, общих параметров, пользовательского словаря и обучение. Чтобы их вызвать нужно получить ссылку на интерфейс ITTSDialogs(перед этим незабыть проверить его поддержку движком), а затем вызвать необходимые диалоги.

Поэтому объявим соответствующую переменную fITTSDialogs типа интерфейса ITTSDialogs, и получим ссылку на интерфейс диалогов при выборе движка:

if (TTSI_ITTSDIALOGS and fpModeInfo^.dwInterfaces) <> 0 then
OleCheck(fITTSCentral.QueryInterface(IID_ITTSDialogs, fITTSDialogs));

А вот методы интерфейса ITTSDialogs, вызывающие соответствующие диалоги:

About — function AboutDlg(hWndParent:
HWND; pszTitle: PAnsiChar): HResult;

Общие параметры — function GeneralDlg(hWndParent: HWND; pszTitle: PAnsiChar): HResult;

Словарь — function LexiconDlg(hWndParent: HWND; pszTitle: PAnsiChar): HResult;

Обучение — function TranslateDlg(hWndParent: HWND; pszTitle: PAnsiChar): HResult;

Заключение

В этом материале я не рассказал как работать с закладками, если чтение осуществляется
с помощью речевых функций SpeechAPI. Это дело поправимое — ждите новых выпусков!

Вот вроде и все, что хотел рассказать на этот раз! Спасибо всем посетителям
сайта за большие и хорошие письма, которые поддерживают меня в написании новых
статей. Надеюсь, что мои статьи помогут вам разобраться в технологии Text-To-Speech.

Готовые проекты:

Демопроект, чтения MSAgent’ом с применением движков — AgentReadTags(zip
— 226Кб)

Проект чтения текстов с применением тегов и изменением атрибутов через API
SpeechTegs(zip
— 243Кб)

Speak up! Let us know what you think.