Speech API в Delphi (часть 2)

Источник:subritto.h1.ru
Дата публикации:2004
Поделиться в Twitter Поделиться в F******k Поделиться в VKontakte Поделиться в Telegram Поделиться в Mastodon

Управление параметрами чтения

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

Теги речевой разметки

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

Вот некоторые правила синтаксиса для речевых тегов:

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

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

А теперь подробнее...

p>\Chr=string\

Устанавливает тип голоса:

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

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

\Ctx=string\

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

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

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

\Emp\

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

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

\Lst\

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

\Map="spokentext"="balloontext"\

Тег указывает как должен отображаться читаемый текст в воздушном шарике. Это удобно, когда нужно прочитать один текст, а показать вместо него другой. Этот тег я активно использую в программке (ссылка ведет на сайт автора статьи - прим. сост.) LittleHelper. Он поддерживает свой собственный словарь произношений, если нужно читать слово "юзер" как "йууузер", то приходится пользоваться этим тегом, чтобы слово в воздушном шарике(в баллоне) выглядело в нормальном виде.

*Естественно, что этот тег тоже поддерживается при чтении MSAgent, а не функциями API.

\Mrk=number\

Тег определяет в тексте закладку. Затем при чтении генерируется событие, которое можно обработать. Рассмотрим это позже.

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

\Pau=number\

Пауза в речи на указанное количество милисекунд.(1сек = 1000 миллисекунд)

\Pit=number\

Указывает тон речи в герцах.

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

\Rst\

Сбрасывает все теги на значения по умолчанию.

\Spd=number\

Скорость речи. Количество слов в минуту.

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

\Vol=number\

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

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

...
Chars.Speak('\spd=60\Привет! Привет! Привет! \spd=120\Как дела? расскажи скорее!', '');
...<
/PRE>
 

Это все! Кроме того, теги не будут отображаться в воздушном шарике. Этот способ единственный, кроме того, он очень оригинальный и простой для конечных разработчиков и пользователей. Ведь даже пользователь сможет вставить в читаемую строку нужный тег.

Еще хотелось бы добавить пару слов про закладки(bookmark). По достижению тега закладки \Mrk=number\ в тексте при чтении у нашего ActiveX-компонента генерируется событие OnBookmark. ропишите все, что вам нужно в этот обработчик и пользуйтесь. Для демонстрации тегов я написал следущий пример:

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

Теперь попытаемся усовершенствовать проект SpeechTest - добавим в него функции изменения тона речи и т.д.! Назовем поэтому его SpeechTegs(ссылка в конце страницы).

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

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

...

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

Еще хотелось бы отметить, что при чтении текста функциями 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;

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

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

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

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

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

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

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

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

....
//Заставляем движок изменить скорость на min
fITTSAttributes.SpeedSet(TTSATTR_MINSPEED);
//Читаем это значение в переменную 
MinSpeedVoicefITTSAttributes.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);

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

Однако, не все так просто. Перед тем как регулировать скорость или еще какие-нибудь атрибуты, а также получать ссылку на интерфейс 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, а из названия констант и так понятно, что ими можно проверить:

< PRE> {bit 1} TTSFEATURE_VOLUME = $00000002 ; {bit 2} TTSFEATURE_SPEED = $00000004 ; {bit 3} TTSFEATURE_PITCH = $00000008 ;

С дополнительной проверкой код изменения скорости речи примет вид:

if (TTSI_ITTSATTRIBUTES and fpModeInfo^.dwInterfaces) <> 0 then 
begin 
try 
{Получаем интерфейс управления атрибутами чтения }
OleCheck(fITTSCentral.QueryInterface(IID_ITTSAttributes, fITTSAttributes));

{Проверяем поддержку изменения скорости побитовым умножением}
 if (TTSFEATURE_SPEED and fpModeInfo.dwFeatures) <> 0 then
 begin
 ...
 end;
 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, вызывающие соответствующие диалоги:

UL>
  • 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.

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



  • Распространение материалов сайта означает, что распространитель принял условия лицензионного соглашения.
    Идея и реализация: © Владимир Довыденков и Анатолий Камынин,  2004-2024