Для более полной информации и глубокого понимания данного вопроса советую преобрести мою книгу «MS Agent и Speech API в Delphi», которая вышла в 2005 году в издательстве БХВ-Петербург. Теме Speech API посвещена отдельная ее глава (более 120 стр).
Если этой книги нет в магазинах вашего города, то ее можно заказать на сайте http://books.ru для получения по почте наложенным платежом.
Краткие основы:
Speech API имеет несколько уровней интерфейсов.
1. Voice Commnad API, Voice Dictation API, Voice Text API
2. Shared Object
3. DirectSpeechRecognition API, DirectTextToSpeech API
4. Audio Object
Интерфейсы самого верхнего уровня (наиболее простые) — 1. Данные интерфейсы предоставляют несколько ограниченный уровень доступа к речевым модулям. Зато интерфейсы управления у них намного проще, чем у интерфейсов низкого уровня(3).
Несмотря на то, что DirectTextToSpeech является более низким интерфейсом прикладного программирования, прямой синтез речи (Text-To-Speech) ничуть не сложнее, чем VoiceText API. Даже наоборот, именно API обладает большей гибкостью, скоростью и, следовательно, мощными возможностями.
DirectTextToSpeech иначе называется интерфейсом прямого синтеза речи, так как при его использовании приходится все делать руками, в том числе устанавливать соединение с аудиоустройством, производить поиск и выбор необходимого речевого модуля в системе и т. д. Например, в случае с VoiceText нам не нужно специально инициализировать звуковое устройство, в которое должны записываться волновые данные. В Direct TextToSpeech все не так. Здесь потребуется познакомиться с рядом объектов из Audio Objects и объектами для работы с движками.
DirectTextToSpeech иначе называется интерфейсом прямого синтеза речи, так как при его использовании приходится все делать руками, в том числе устанавливать соединение с аудиоустройством, производить поиск и выбор необходимого речевого модуля в системе и т. д. Например, в случае с VoiceText нам не нужно специально инициализировать звуковое устройство, в которое должны записываться волновые данные. В Direct TextToSpeech все не так. Здесь потребуется познакомиться с рядом объектов из Audio Objects и объектами для работы с движками.
В SAPI имеется ряд базовых аудио объектов для обращения и отправления звуковых данных на аудиоустройство. Причем под аудиоустройством здесь понимается не только физическое устройство в виде звуковой карты, а еще и волновой файл.
- DirectSound Audio Destination
- Multimedia Audio Destination
- File Audio Destination
Объект DirectSound Audio Destination самый быстрый, так как посылает всё аудио на растерзание DirectSound API из DirectX. Как следствие этот объект можно(и нужно, если понадобится синтез речи) использовать только в играх, включающих голосовые технологии. Для обычных приложений по удобству и привычности лучше всего подойдет объект Multimedia Audio Destination. Если синтез речи должен осуществляться в звуковой файл, то следует использовать File Audio Destination.
Из имеющихся сведений вполне может обозначиться алгоритм работы. Схема достижения синтеза речи через Direct TextToSpeech API такова:
1. Осуществить подключение с аудиоустройству через один из возможных интерфейсов. При необходимости выбрать нужное устройство. Если требуется, то получить указатели на дополнительные интерфейсы;
2. Произвести выбор речевого модуля. Обычно по причине нашего Windows-интерфейса на этом этапе происходит поиск всех установленных модулей(синтезаторов), но выбор остается за пользователем, ему все-таки виднее;
3. После того, как пользователь выбрал модуль или вы программно определились какой из TTS-генераторов будет использоваться для синтеза речи, то данный режим нужно применить, при этом возвращается указатель на центральный интерфейс Engine Object;
4. Затем по усмотрению регистрируется объект, реализующий ITTSNotifySink, который будет получать уведомления о ходе синтеза речи;
5. Наконец, в нужный момент передается буфер текста в очередь на синтез и указатель на объект, получающий уведомления ITTSBufNotifySink;
Обработка уведомлений(Notification Sink)
Синтез речи на звуковую карту описан в предыдущей статье «Speech API в Delphi». Во многих случаях, если не во всех, желательно указывать какое место текста читает программа. Для этого и некоторых других функциий необходимо использовать интерфейсы обработки уведомлений Notification Sink.
Engine Object поддерживает два интерфейса уведомлений —ITTSNotifySink и ITTSBufNotifySink, первый посылает уведомления о состоянии чтения, второй — о состоянии буфера чтения. Для получения уведомлений DirectTextToSpeech нужно создать объект, реализующий данный интерфейс. Объект, реализующий интерфейс ITTSBufNotifySink, не нужно регистрировать через специальный метод, интерфейс этих уведомлений не меняется при смене устройства назначения аудио данных. Поэтому-то все извещения разнесены по нескольким интерфейсам.
Для обработки уведомлений необходимо создать специальный класс, реализующий интерфейс ITTSBufNotifySink, в классе реализовать необходимые методы. Ссылку на экземпляр класса передать в интерфейс TTSCentral.
Для определения позиции читаемых слов необходимо реализовать метод WordPosition. Например так:
Чтобы определить начало и конец чтения вообще, необходимо обработать события AudioStart и AudioStop интрфейса ITTSNotifySink.
Вот список поддерживаемых методов интерфейсом ITTSNotifySink:
Методы интерфейса ITTSBufNotifySink:
function WordPosition(qTimeStamp: QWORD; dwByteOffset: DWORD): HResult; stdcall;
Простейшее приложение, выделяющее читаемый текст, может выглядеть так:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Speech, ActiveX, ComObj;
type
TTSBufNotifySink = class;
TTSNotifySink = class;
TForm1 = class(TForm)
RichEdit1: TRichEdit;
ControlBar1: TControlBar;
ToolBar1: TToolBar;
Speak: TSpeedButton;
PauseSpeak: TSpeedButton;
Stop: TSpeedButton;
ComboBox1: TComboBox;
Splitter1: TSplitter;
StatusBar1: TStatusBar;
ListBox1: TListBox;
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure SpeakClick(Sender: TObject);
procedure PauseSpeakClick(Sender: TObject);
procedure StopClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
fITTSCentral: ITTSCentral; {Центральный интерфейс,
через который производятся все действия с речью}
fIAMM: IAudioMultimediaDevice;
{Интерфейс для связи с аудио устройством}
aTTSEnum: ITTSEnum;
{Интерфейс для перебора движков}
fpModeInfo: PTTSModeInfo;
{Указатель на параметры движка}
Pause: Boolean;
fTTSNotifySink : ITTSNotifySink;
fTTSBufNotifySink : ITTSBufNotifySink;
fdwKey: DWord;
procedure AddLog(const s: string);
public
{ Public declarations }
end;
TTSBufNotifySink = class(TInterfacedObject, ITTSBufNotifySink)
private
fForm : TForm1;
protected
function TextDataDone(qTimeStamp: QWORD; dwFlags: DWORD): HResult; stdcall;
function TextDataStarted(qTimeStamp: QWORD) : HResult; stdcall;
function BookMark(qTimeStamp: QWORD; dwMarkNum: DWORD) : HResult; stdcall;
function WordPosition(qTimeStamp: QWORD; dwByteOffset: DWORD) : HResult; stdcall;
public
constructor create(aForm : TForm1);
end; //ITTSBufNotifySink
TTSNotifySink = class(TInterfacedObject, ITTSNotifySink)
private
fForm : TForm1;
protected
function AttribChanged(dwAttribute: DWORD) : HResult; stdcall;
function AudioStart(qTimeStamp: QWORD) : HResult; stdcall;
function AudioStop(qTimeStamp: QWORD) : HResult; stdcall;
function Visual(qTimeStamp: QWORD;
cIPAPhoneme: Char;
cEnginePhoneme: Char;
dwHints: DWORD;
apTTSMouth: PTTSMouth) : HResult; stdcall;
public
constructor create(aForm : TForm1);
end; //TTSNotifySink
var
Form1: TForm1;
implementation
{$R *.DFM}
{--------------------TTSBufNotifySink ----------------------}
constructor TTSBufNotifySink.create(aForm : TForm1);
begin
fForm := aForm;
end;
function TTSBufNotifySink.TextDataDone(qTimeStamp: QWORD; dwFlags: DWORD) : HResult;
begin
result := 0;
fForm.AddLog('Событие TextDataDone. TimeStamp :' +
IntToStr(qTimeStamp) + ' Flags :' + IntToStr(dwFlags));
fForm.StatusBar1.Panels.Items[1].Text:='TextDataDone...';
fForm.Update;
if Assigned(fForm.fTTSBufNotifySink) then
begin
fForm.Speak.Down:=False;
fForm.Stop.Down:=False;
end;
end;
function TTSBufNotifySink.TextDataStarted(qTimeStamp: QWORD): HResult;
begin
result := 0;
fForm.AddLog('Событие TextDataStarted. TimeStamp: ' +
IntToStr(qTimeStamp));
end;
function TTSBufNotifySink.BookMark(qTimeStamp: QWORD; dwMarkNum: DWORD): HResult;
begin
result := 0;
fForm.AddLog('Событие BookMark. TimeStamp :' +
IntToStr(qTimeStamp) + ' MarkNum :' + IntToStr(dwMarkNum));
end;
function TTSBufNotifySink.WordPosition(qTimeStamp: QWORD; dwByteOffset: DWORD): HResult;
begin
result := 0;
fForm.AddLog('Событие WordPosition. TimeStamp :' +
IntToStr(qTimeStamp) + ' Offset: ' + IntToStr(dwByteOffset));
fForm.RichEdit1.SelStart:=0;
fForm.RichEdit1.SelLength:=dwByteOffset;
end;
{--------------------------TTSNotifySink-------------------------}
constructor TTSNotifySink.create(aForm : TForm1);
begin
fForm := aForm;
end;
function TTSNotifySink.AttribChanged(dwAttribute: DWORD) : HResult;
var
S:String;
begin
result := 0;
case dwAttribute of
TTSNSAC_LANGUAGE : S := 'Язык';
TTSNSAC_REALTIME : S := 'Приоритет процессорного времени';
TTSNSAC_PITCH : S := 'Тон';
TTSNSAC_SPEED : S := 'Скорость';
TTSNSAC_VOLUME : S := 'Громкость';
else
S := 'Attr='+IntToStr(dwAttribute);
end;
fForm.AddLog('Событие AttribChanged. Attribute :' + S);
end;
function TTSNotifySink.AudioStart(qTimeStamp: QWORD) : HResult;
begin
result := 0;
fForm.AddLog('Событие AudioStart, TimeStamp :' + IntToStr(qTimeStamp));
fForm.StatusBar1.Panels.Items[0].Text:='Читаю';
end;
function TTSNotifySink.AudioStop(qTimeStamp: QWORD) : HResult;
begin
result := 0;
fForm.AddLog('Событие AudioStop. TimeStamp :' + IntToStr(qTimeStamp));
fForm.StatusBar1.Panels.Items[0].Text:='Стоп';
fForm.Speak.Down:=false;
end;
function TTSNotifySink.Visual(qTimeStamp: QWORD;
cIPAPhoneme: Char;
cEnginePhoneme: Char;
dwHints: DWORD;
apTTSMouth: PTTSMouth) : HResult;
var
tmp: String;
begin
result := 0;
if cEnginePhoneme = '@' then
exit;
tmp := '';
if dwHints <> 0 then
begin
If (TTSNSHINT_QUESTION and dwHints) <> 0 then
tmp := tmp + 'Question ';
If (TTSNSHINT_STATEMENT and dwHints) <> 0 then
tmp := tmp + 'Statement ';
If (TTSNSHINT_COMMAND and dwHints) <> 0 then
tmp := tmp + 'Command ';
If (TTSNSHINT_EXCLAMATION and dwHints) <> 0 then
tmp := tmp + 'Exclamation ';
If (TTSNSHINT_EMPHASIS and dwHints) <> 0 then
tmp := tmp + 'Emphasis ';
end
else
tmp := 'None';
fForm.AddLog('Событие Visual. TStamp :'+FloatToStr(qTimeStamp));
fForm.AddLog(' IPAPhoneme :' + IntToStr(ord(cIPAPhoneme)));
fForm.AddLog(' Phoneme :' + cEnginePhoneme);
fForm.AddLog(' Hints :' + tmp);
fForm.AddLog(' Data :'+IntToStr(apTTSMouth^.bMouthHeight)+','+
IntToStr(apTTSMouth^.bMouthWidth));
end;
{---------------------------------------------------------------------------}
procedure TForm1.AddLog(const s: string);
begin
Form1.ListBox1.Items.Add(s);
Form1.ListBox1.ItemIndex:=Form1.ListBox1.Items.Count-1;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
NumFound : DWord;
ModeInfo : TTSModeInfo;
begin
try
{Инициализация аудио устройства}
CoCreateInstance(CLSID_MMAudioDest, Nil, CLSCTX_ALL,
IID_IAudioMultiMediaDevice, fIAMM);
except
end;
{Создание перечисляемого объекта для перебора всех движков в системе
с помощью интерфейса ITTSEnum}
CoCreateInstance(CLSID_TTSEnumerator, Nil, CLSCTX_ALL, IID_ITTSEnum, aTTSEnum);
aTTSEnum.Reset;//Сбрасываем на первый
aTTSEnum.Next(1, ModeInfo, @NumFound); {Получаем первый движок}
While NumFound > 0 do
begin
ComboBox1.Items.Add(String(ModeInfo.szModeName));
aTTSEnum.Next(1, ModeInfo, @NumFound); {Получаем остальные}
end;
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
NumFound: DWord;
ModeInfo : TTSModeInfo;{Для хранения информации о текущем движке}
begin
try
CoCreateInstance(CLSID_MMAudioDest, Nil, CLSCTX_ALL,
IID_IAudioMultiMediaDevice, fIAMM);
CoCreateInstance(CLSID_TTSEnumerator, Nil, CLSCTX_ALL, IID_ITTSEnum, aTTSEnum);
aTTSEnum.Reset;
{Перескакиваем на нужный движок}
Form1.aTTSEnum.skip(ComboBox1.ItemIndex);
aTTSEnum.Next(1, ModeInfo, @NumFound);
if assigned(fpModeInfo) then
{если fpModeInfo не равен nil}
dispose(fpModeInfo);
new(fpModeInfo);
fpModeInfo^:=ModeInfo;
{загружаем движок}
aTTSEnum.Select(fpModeInfo^.gModeID, fITTSCentral, IUnknown(fIAMM));
try
fTTSBufNotifySink := TTSBufNotifySink.Create(Self);
AddLog('Создание BufNotifySink прошло успешно');
except
AddLog('Создание BufNotifySink неудачно');
end;
try
fTTSNotifySink := TTSNotifySink.Create(Self);
AddLog('Создание Engine NotifySink успешно');
try
OleCheck(fITTSCentral.Register(pointer(fTTSNotifySink),
IID_ITTSNotifySink, fdwKey));
AddLog('Регистрация Engine NotifySink завершилась успешно');
except
fTTSNotifySink := nil;
AddLog('Регистрация Engine NotifySink прошла неудачно');
end;
except
AddLog('Создание Engine NotifySink неудачно');
end;
except
end;
end;
procedure TForm1.SpeakClick(Sender: TObject);
var
fSData: TSData;
BufRich: string;
begin
if not assigned(fITTSCentral) then
begin
ShowMessage('Не выбран движок!');
exit;
end;
if pause then
begin
try
fITTSCentral.AudioResume;
Pause:=False;
except
exit;
end;
end
else
begin
RichEdit1.SetFocus;
BufRich:=copy(RichEdit1.Text, RichEdit1.SelStart,
length(RichEdit1.Text)-RichEdit1.SelStart);
fSData.dwSize := length(BufRich) + 1;
fSData.pData := pChar(BufRich);
try
fITTSCentral.TextData(CHARSET_TEXT, 1, fSData, pointer(fTTSBufNotifySink),
IID_ITTSBufNotifySink);
except
end;
end;
Speak.Down:=true;
end;
procedure TForm1.PauseSpeakClick(Sender: TObject);
begin
Speak.Down:=false;
PauseSpeak.Down:=true;
if not assigned(fITTSCentral) then
begin
ShowMessage('Не выбран движок!');
exit;
end;
if Pause then
exit;
try
fITTSCentral.AudioPause;
pause:=True;
except
end;
end;
procedure TForm1.StopClick(Sender: TObject);
begin
Speak.Down:=false;
if not assigned(fITTSCentral) then
begin
showmessage('Не выбран движок!');
exit;
end;
try
fITTSCentral.AudioReset;
Pause:=False;
except
end;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(fITTSCentral) then
fITTSCentral.AudioReset;
if fdwKey<>0 then
try
fITTSCentral.UnRegister(fdwKey);
fTTSNotifySink:= Nil;
except
end;
try
fTTSBufNotifySink:=nil;
except
end;
end;
end.
Синтез речи в волновой файл
В ходе работы со звуковой картой инциализация устройства выглядела следующим образом:
CoCreateInstance(CLSID_MMAudioDest, Nil, CLSCTX_ALL,
IID_IAudioMultiMediaDevice, fIAMM)
Теперь она немного изменится, необходимо подлючаться к File Audio Destination. Основной интерфейс управления объекта File Audio Destination — IAudioFile, через него осуществляются все основные манипуляции с файлом.
var
fIAF: IAudioFile;
При инициализации приложения нужно получать интерфейс на IAudioFile.
CoCreateInstance(CLSID_AudioDestFile, Nil, CLSCTX_ALL,
IID_IAudioFile, fIAF);
Далее схема не нарушается. Также осуществляется поиск движка и его выбор, однако, при выборе синтезатора методом Select мы передаем указатель на интерфейс аудио файла: aTTSEnum.Select(fpModeInfo^.gModeID, fITTSCentral, IUnknown(fIAF));
Определение имени конкретного файла, в который будут записываться аудио данные, лучше сделать перед чтением. Указание имени файла реализуется через интерфейс IAudioFile методом DoSet. Данный метод сбрасывает с очереди все аудио файлы и ставит в нее текущий. В параметрах нужно указывать имя файла в Unicode и идентификатор файла, который будет посылаться при старте и завершении записи файла в соответствующих уведомлениях. Допустим, в поле редактирование Edit1 введено имя файла, тогда код, запускающий синтез речи в файл, примет вид:
var fSData: TSData; FlName: PWideChar; fID: Cardinal; fIAF: IAudioFile;
Когда чтение завершится, то вам может показаться странным, что размер файла остался нулевым, однако, при закрытии программы он все-таки приобретет свой законный размер. Происходит это, потому что после завершения чтения нужно сбрасывать файл из очереди, как при работе с обычными файлами, которые нужно закрывать, чтобы они не испортились, здесь же файл нужно просто освобождать, чтобы можно было к нему обратиться. Делается это методом Flush, его следует разместить в обработке уведомления, например, в AudioStop интерфейса ITTSNotifySink или ITTSBufNotifySink.TextDataDone. function TTSNotifySink.AudioStop(qTimeStamp: QWORD) : HResult; begin result := S_OK; fForm.fIAF.Flush; end; Для того, чтобы увеличить скорость записи в файл и тем самым уменьшить время ожидания, нужно применить метод RealTimeSet интерфейса IAudioFile. В виде параметра здесь передается значение, символизирующее скорость. Так, реальная скорость(однократная) имеет значение 256 или в шестнадцатиричной форме $100, двухкратная — $200, четырех — $400, и т. д. Странно, что в Speech API SDK не указан верхний предел скорости, но сразу можно сказать о физическом пределе — это размер типа WORD — 65535. Проверка этого параметра закончилась положительно, но скорость была такой же, как шестнадцатикратная.Простейшая программа синтезирующая речь в файл:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Buttons, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Speech, ActiveX, ComObj;
type
TTSBufNotifySink = class;
TAudioFileNotifySink = class;
TForm1 = class(TForm)
RichEdit1: TRichEdit;
ControlBar1: TControlBar;
ToolBar1: TToolBar;
Speak: TSpeedButton;
PauseSpeak: TSpeedButton;
Stop: TSpeedButton;
ComboBox1: TComboBox;
Splitter1: TSplitter;
StatusBar1: TStatusBar;
ToolBar2: TToolBar;
Edit1: TEdit;
SpeedButton1: TSpeedButton;
SaveDialog1: TSaveDialog;
ListBox1: TListBox;
Panel4: TPanel;
cbSpeed: TComboBox;
procedure FormCreate(Sender: TObject);
procedure ComboBox1Change(Sender: TObject);
procedure SpeakClick(Sender: TObject);
procedure PauseSpeakClick(Sender: TObject);
procedure StopClick(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure SpeedButton1Click(Sender: TObject);
procedure cbSpeedChange(Sender: TObject);
private
{ Private declarations }
fITTSCentral: ITTSCentral; {Центральный интерфейс,
через который производятся все действия с речью}
fIAF: IAudioFile;
{Интерфейс для связи с аудиофайлом}
aTTSEnum: ITTSEnum;
{Интерфейс для перебора движков}
fpModeInfo: PTTSModeInfo;
{Указатель на параметры движка}
fAudioFileNotifySink : IAudioFileNotifySink;
fTTSNotifySink : ITTSNotifySink;
fTTSBufNotifySink : ITTSBufNotifySink;
Pause: Boolean;
fID: Cardinal;
fdwKey: DWord;
FlName: PWideChar;
procedure AddLog(s: string);
public
{ Public declarations }
end;
TTSBufNotifySink = class(TInterfacedObject, ITTSBufNotifySink)
private
fForm : TForm1;
protected
function TextDataDone(qTimeStamp: QWORD; dwFlags: DWORD): HResult; stdcall;
function TextDataStarted(qTimeStamp: QWORD) : HResult; stdcall;
function BookMark(qTimeStamp: QWORD; dwMarkNum: DWORD) : HResult; stdcall;
function WordPosition(qTimeStamp: QWORD; dwByteOffset: DWORD) : HResult; stdcall;
public
constructor create(aForm : TForm1);
end; //ITTSBufNotifySink
TTSNotifySink = class(TInterfacedObject, ITTSNotifySink)
private
fForm : TForm1;
protected
function AttribChanged(dwAttribute: DWORD) : HResult; stdcall;
function AudioStart(qTimeStamp: QWORD) : HResult; stdcall;
function AudioStop(qTimeStamp: QWORD) : HResult; stdcall;
function Visual(qTimeStamp: QWORD;
cIPAPhoneme: Char;
cEnginePhoneme: Char;
dwHints: DWORD;
apTTSMouth: PTTSMouth) : HResult; stdcall;
public
constructor create(aForm : TForm1);
end; //TTSNotifySink
TAudioFileNotifySink = class(TInterfacedObject, IAudioFileNotifySink)
private
fForm : TForm1;
protected
function FileBegin(dwID: DWORD): HResult; stdcall;
function FileEnd(dwID: DWORD): HResult; stdcall;
function QueueEmpty: HResult; stdcall;
function Posn(qwProcessed: QWORD; qwLeft: QWORD): HResult; stdcall;
public
constructor Create(aForm : TForm1);
end; //TTSNotifySink
var
Form1: TForm1;
implementation
{$R *.DFM}
{-------------------------TTSBufNotifySink ---------------------------------------}
constructor TTSBufNotifySink.Create(aForm : TForm1);
begin
fForm := aForm;
end;
function TTSBufNotifySink.TextDataDone(qTimeStamp: QWORD; dwFlags: DWORD) : HResult;
begin
result := S_OK;
fForm.AddLog('Событие TextDataDone. TimeStamp :' +
FloatToStr(qTimeStamp) + ' Flags :' + IntToStr(dwFlags));
fForm.StatusBar1.Panels.Items[1].Text:='TextDataDone...';
fForm.RichEdit1.SelectAll;
fForm.Update;
fForm.Speak.Down:=false;
if Assigned(fForm.fTTSBufNotifySink) then
begin
fForm.Speak.Down:=False;
fForm.Stop.Down:=False;
end;
end;
function TTSBufNotifySink.TextDataStarted(qTimeStamp: QWORD): HResult;
begin
result := S_OK;
fForm.AddLog('Событие TextDataStarted. TimeStamp: ' +
FloatToStr(qTimeStamp));
end;
function TTSBufNotifySink.BookMark(qTimeStamp: QWORD; dwMarkNum: DWORD): HResult;
begin
result := S_OK;
fForm.AddLog('Событие BookMark. TimeStamp :' +
FloatToStr(qTimeStamp) + ' MarkNum :' + IntToStr(dwMarkNum));
end;
function TTSBufNotifySink.WordPosition(qTimeStamp: QWORD; dwByteOffset: DWORD): HResult;
begin
result := S_OK;
fForm.AddLog('Событие WordPosition. TimeStamp :' +
FloatToStr(qTimeStamp) + ' Offset: ' + IntToStr(dwByteOffset));
fForm.RichEdit1.SelStart:=0;
fForm.RichEdit1.SelLength:=dwByteOffset;
end;
{--------------------------TTSNotifySink-------------------------}
constructor TTSNotifySink.create(aForm : TForm1);
begin
fForm := aForm;
end;
function TTSNotifySink.AttribChanged(dwAttribute: DWORD) : HResult;
var
S:String;
begin
result := S_OK;
case dwAttribute of
TTSNSAC_LANGUAGE : S := 'Язык';
TTSNSAC_REALTIME : S := 'Приоритет процессорного времени';
TTSNSAC_PITCH : S := 'Тон';
TTSNSAC_SPEED : S := 'Скорость';
TTSNSAC_VOLUME : S := 'Громкость';
else
S := 'Attr='+IntToStr(dwAttribute);
end;
fForm.AddLog('Событие AttribChanged. Attribute :' + S);
end;
function TTSNotifySink.AudioStart(qTimeStamp: QWORD) : HResult;
begin
result := S_OK;
fForm.AddLog('Событие AudioStart, TimeStamp :' + IntToStr(qTimeStamp));
fForm.StatusBar1.Panels.Items[0].Text:='Читаю';
end;
function TTSNotifySink.AudioStop(qTimeStamp: QWORD) : HResult;
begin
result := S_OK;
fForm.fIAF.Flush;
fForm.AddLog('Событие AudioStop. TimeStamp :' + IntToStr(qTimeStamp));
fForm.StatusBar1.Panels.Items[0].Text:='Стоп';
fForm.Speak.Down:=false;
end;
function TTSNotifySink.Visual(qTimeStamp: QWORD;
cIPAPhoneme: Char;
cEnginePhoneme: Char;
dwHints: DWORD;
apTTSMouth: PTTSMouth) : HResult;
var
tmp: String;
begin
result := 0;
if cEnginePhoneme = '@' then
exit;
tmp := '';
if dwHints <> 0 then
begin
If (TTSNSHINT_QUESTION and dwHints) <> 0 then
tmp := tmp + 'Question ';
If (TTSNSHINT_STATEMENT and dwHints) <> 0 then
tmp := tmp + 'Statement ';
If (TTSNSHINT_COMMAND and dwHints) <> 0 then
tmp := tmp + 'Command ';
If (TTSNSHINT_EXCLAMATION and dwHints) <> 0 then
tmp := tmp + 'Exclamation ';
If (TTSNSHINT_EMPHASIS and dwHints) <> 0 then
tmp := tmp + 'Emphasis ';
end
else
tmp := 'None';
fForm.AddLog('Событие Visual. TStamp :'+FloatToStr(qTimeStamp));
fForm.AddLog(' IPAPhoneme :' + IntToStr(ord(cIPAPhoneme)));
fForm.AddLog(' Phoneme :' + cEnginePhoneme);
fForm.AddLog(' Hints :' + tmp);
fForm.AddLog(' Data :'+IntToStr(apTTSMouth^.bMouthHeight)+','+
IntToStr(apTTSMouth^.bMouthWidth));
end;
{----------------------- IAudioFileNotifySink ------------------------}
constructor TAudioFileNotifySink.create(aForm : TForm1);
begin
fForm := aForm;
end;
function TAudioFileNotifySink.FileBegin(dwID: DWORD): HResult; stdcall;
begin
fForm.StatusBar1.Panels.Items[0].Text:='Пишу в файл('+IntToStr(dwID)+')';
Result:=0;
end;
function TAudioFileNotifySink.FileEnd(dwID: DWORD): HResult; stdcall;
begin
fForm.StatusBar1.Panels.Items[0].Text:='Готово('+IntToStr(dwID)+')';
Result:=0;
end;
function TAudioFileNotifySink.QueueEmpty: HResult; stdcall;
begin
//fForm.StatusBar1.Panels.Items[0].Text:='Пусто';
Result:=0;
end;
function TAudioFileNotifySink.Posn(qwProcessed: QWORD; qwLeft: QWORD): HResult; stdcall;
begin
fForm.AddLog('Событие Posn. qwProcessed: '+IntToStr(qwProcessed)+
', qwLeft: '+IntToStr(qwLeft));
Result:=0;
end;
{---------------------------------------------------------------------------}
procedure TForm1.AddLog(s: string);
begin
Form1.ListBox1.Items.Add(s);
Form1.ListBox1.ItemIndex:=Form1.ListBox1.Items.Count-1;
end;
procedure TForm1.FormCreate(Sender: TObject);
var
NumFound : DWord;
ModeInfo : TTSModeInfo;
begin
try
{Инициализация аудио файла}
CoCreateInstance(CLSID_AudioDestFile, Nil, CLSCTX_ALL, IID_IAudioFile, fIAF);
except
end;
{Создание перечисляемого объекта для перебора всех движков в системе
с помощью интерфейса ITTSEnum}
CoCreateInstance(CLSID_TTSEnumerator, Nil, CLSCTX_ALL, IID_ITTSEnum, aTTSEnum);
aTTSEnum.Reset;//Сбрасываем на первый
aTTSEnum.Next(1, ModeInfo, @NumFound); {Получаем первый движок}
while NumFound > 0 do
begin
ComboBox1.Items.Add(String(ModeInfo.szModeName));
aTTSEnum.Next(1, ModeInfo, @NumFound); {Перескакиваем на следующий}
end;
end;
procedure TForm1.ComboBox1Change(Sender: TObject);
var
NumFound: DWord;
ModeInfo: TTSModeInfo;{Для хранения информации о текущем движке}
begin
try
aTTSEnum.Reset;
{Перескакиваем на нужный движок}
aTTSEnum.skip(ComboBox1.ItemIndex);
aTTSEnum.Next(1, ModeInfo, @NumFound);
if assigned(fpModeInfo) then
{если fpModeInfo не равен nil}
dispose(fpModeInfo);
new(fpModeInfo);
fpModeInfo^:=ModeInfo;
{загружаем движок}
aTTSEnum.Select(fpModeInfo^.gModeID, fITTSCentral, IUnknown(fIAF));
try
fTTSBufNotifySink := TTSBufNotifySink.Create(Self);
AddLog('Создание BufNotifySink прошло успешно');
except
AddLog('Создание BufNotifySink неудачно');
end;
try
fTTSNotifySink := TTSNotifySink.Create(Self);
AddLog('Создание Engine NotifySink успешно');
try
OleCheck(fITTSCentral.Register(pointer(fTTSNotifySink),
IID_ITTSNotifySink, fdwKey));
AddLog('Регистрация Engine NotifySink завершилась успешно');
except
fTTSNotifySink := nil;
AddLog('Регистрация Engine NotifySink прошла неудачно');
end;
except
AddLog('Создание Engine NotifySink неудачно');
end;
try
fAudioFileNotifySink := TAudioFileNotifySink.Create(Self);
AddLog('Создание AudioFileNotifySink прошло успешно');
try
OleCheck(fIAF.Register(fAudioFileNotifySink));
AddLog('Регистрация AudioFileNotifySink прошло успешно');
except
fAudioFileNotifySink := nil;
AddLog('Регистрация AudioFileNotifySink неудачно');
end;
except
AddLog('Создание AudioFileNotifySink неудачно');
end;
except
end;
end;
procedure TForm1.SpeakClick(Sender: TObject);
var
fSData: TSData;
BufRich: string;
begin
if not assigned(fITTSCentral) then
begin
ShowMessage('Не выбран движок!');
exit;
end;
if Edit1.Text='' then
begin
ShowMessage('Не указан файл');
exit;
end;
if pause then
begin
try
fITTSCentral.AudioResume;
Pause:=False;
except
exit;
end;
end
else
begin
RichEdit1.SetFocus;
FlName:=StringToOleStr(Edit1.Text);
fID:=1;
fIAF.DoSet(FlName, fID);
BufRich:=copy(RichEdit1.Text, RichEdit1.SelStart,
length(RichEdit1.Text)-RichEdit1.SelStart);
fSData.dwSize:= length(BufRich) + 1;
fSData.pData:= pChar(BufRich);
try
fITTSCentral.TextData(CHARSET_TEXT, 0, fSData, pointer(fTTSBufNotifySink),
IID_ITTSBufNotifySink);
except
end;
end;
Speak.Down:=true;
end;
procedure TForm1.PauseSpeakClick(Sender: TObject);
begin
Speak.Down:=false;
PauseSpeak.Down:=true;
if not assigned(fITTSCentral) then
begin
ShowMessage('Не выбран движок!');
exit;
end;
if Pause then
exit;
try
fITTSCentral.AudioPause;
pause:=True;
except
end;
end;
procedure TForm1.StopClick(Sender: TObject);
begin
Speak.Down:=false;
if not assigned(fITTSCentral) then
begin
showmessage('Не выбран движок!');
exit;
end;
try
fITTSCentral.AudioReset;
Pause:=False;
except
end;
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if SaveDialog1.Execute then
Edit1.Text:=SaveDialog1.FileName;
end;
procedure TForm1.cbSpeedChange(Sender: TObject);
var
RTime: Word;
spd: integer;
begin
spd:=cbSpeed.ItemIndex;
if spd=cbSpeed.Items.Count-1 then
RTime:=MAXWORD
else
RTime:=256 shl spd;
fIAF.RealTimeSet(RTime);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if Assigned(fITTSCentral) then
fITTSCentral.AudioReset;
try
fAudioFileNotifySink:=nil;
except
end;
try
fTTSBufNotifySink:=nil;
except
end;
end;
end.
Для более полного и глубокого понимания советую преобрести мою книгу «MSAgent и SpeechAPI в Delphi», которая вышла в 2005 году в издательстве БХВ-Петербург.
Заказ книги для получения ее по наиболее удобному для вас способу, в том числе и по почте, наложенным платежом осуществляется на сайте http://books.ru, по адресу: http://www.books.ru/shop/books/239205

PDF
