(***************************************************************************
* eSpeak_Example.pas
* --------------------
* begin : Wednesday, April 15, 2009
* copyright : (C) 2009 Matthias Rolf
* email : m.rolf@gmx.net
* website : http://www.rolfware.de/delphi/espeak_example.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* about this file:
* An example showing how eSpeak (a speech synthesizer) can be integrated
* in Delphi 3.
* The target was to have only one instance of eSpeak running. The user can
* pass text by text and it will be stored in a pipeline. The pipeline will
* be worked off.
*
* you find eSpeak here:
* http://espeak.sourceforge.net
*
***************************************************************************)
unit eSpeak_Example;
interface
uses // may be you don't need all this units so delete unneeded
Windows, SysUtils, Classes, Controls, Forms, StdCtrls, RXSpin, ComCtrls,
ExtCtrls, FmxUtils, ShellApi, Registry;
type
TfrmEspeak = class(TForm)
fnmEspeak: TFilenameEdit; // holds the path and file name of eSpeak
timEspeak: TTimer; // timer to execute the speeches
edtVoice: TRxSpinEdit; // accepts values between -4 and 6
edtWordsPerMinute: TRxSpinEdit; // accepts values between 0 and 200
edtEspeakVolume: TRxSpinEdit; // accepts values between 80 and 390
edtEspeakModify: TEdit; // holds a string with eSpeak parameter (is hidden on the form)
edtEspeakTest: TEdit; // the user can type in his text
btnEspeakTest: TButton; // perform the text from edtEspeakTest
{... other objects}
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure timEspeakTimer(Sender: TObject);
procedure eSpeakModify(Sender: TObject);
procedure btnEspeakTestClick(Sender: TObject);
{... further procedures}
private
{ Private-Declarations }
EspeakHandle : THandle; // the handle from the last executed espeak.exe
timEspeakProcessing, // procedure OnTimer is working
NextEspeakProcessing : Boolean; // procedure NextEspeak is working
strEspeak : TStrings; // pipeline with text to speak
procedure NextEspeak;
procedure FindEspeak;
public
{ Public-Declarations }
EspeakExists : Boolean; // eSpeak is installed
procedure eSpeak(const OutputText : String);
end;
var
frmEspeak: TfrmEspeak;
const
ESPEAK_PROGRAM = 'espeak.exe';
ESPEAK_WITH_PATH = 'command_line\'+ESPEAK_PROGRAM;
CMD_CLASS = 'ConsoleWindowClass';
LANGUAGE = '-v en'; // exchange 'en' with you language e.g. 'de'
ESPEAK_PARAMETER = ' -a %d -s %d "';
implementation
{$R *.DFM}
procedure TfrmEspeak.FormCreate(Sender: TObject);
begin
{scale of window}
Scaled := TRUE;
PixelsPerInch := Screen.PixelsPerInch;
{initialize variables}
EspeakHandle := 0;
timEspeakProcessing := False;
NextEspeakProcessing := False;
EspeakExists := False;
{output parameters: ' -a 100 -s 170 "'}
edtEspeakModify.Text := Format(ESPEAK_PARAMETER,[100,170]);
{assign procedures}
timEspeak.OnTimer := timEspeakTimer;
edtVoice.OnChange := eSpeakModify;
edtWordsPerMinute.OnChange := eSpeakModify;
edtEspeakVolume.OnChange := eSpeakModify;
btnEspeakTest.OnClick := btnEspeakTestClick;
{create TStrings}
strEspeak := TStringList.Create;
{exists eSpeak?}
FindEspeak;
end;
procedure TfrmEspeak.FormDestroy(Sender: TObject);
begin
{release TStrings}
strEspeak.Free;
frmEspeak := nil;
end;
procedure TfrmEspeak.FindEspeak;
var
myReg : TRegistry;
begin
{fnmEspeak.Text might be pre-filled from a Ini file then it is not necessary to search}
if (fnmEspeak.Text = '') then
begin
//HKEY_CLASSES_ROOT\TypeLib\{7192AA2F-F759-43E9-91E7-226371EF6B2F}\1.0\HELPDIR
// e.g.: C:\Program\eSpeak\
myReg := TRegistry.Create;
try
myReg.RootKey := HKEY_CLASSES_ROOT;
if myReg.OpenKey('TypeLib\{7192AA2F-F759-43E9-91E7-226371EF6B2F}\1.0\HELPDIR\', False) then
begin
fnmEspeak.Text := myReg.ReadString('') + ESPEAK_WITH_PATH;
// btnSaveToIniClick(nil); Save in Ini file or registry (if you like)
end;
finally
myReg.Free;
end;
end;
{is Espeak installed?}
EspeakExists := FileExists(fnmEspeak.FileName);
end;
{this procedure accet text and put it in the pipeline}
procedure TfrmEspeak.eSpeak(const OutputText : String);
begin
{pipelining}
strEspeak.Append(OutputText);
{if currently eSpeak is not processing then start it}
if not timEspeak.Enabled and not timEspeakProcessing then
begin
timEspeak.Interval := 10;
{with timer on the processing is started}
timEspeak.Enabled := True;
end;
end;
{the timer periodically checks whether the processing is finished}
procedure TfrmEspeak.timEspeakTimer(Sender: TObject);
var
buff: array [0..127] of Char;
begin
{processing started and therefore stop the timer}
timEspeakProcessing := True;
timEspeak.Enabled := False;
try
{timer interval: half a second to have a pause between speeches}
timEspeak.Interval := 500;
{seach a previously started eSpeak}
GetClassName(EspeakHandle, buff, SizeOf(buff));
if (buff <> CMD_CLASS) then
begin
{eSpeak is not running so the next text can be spoken}
NextEspeak;
end;
finally
{processing is done}
timEspeakProcessing := False;
{start the timer if something is in the pipeline left}
if (strEspeak.Count > 0) then
timEspeak.Enabled := True;
end;
end;
{this is the main procedure which starts eSpeak}
procedure TfrmEspeak.NextEspeak;
var
OutputText : String;
buff: array [0..127] of Char;
Wnd: hWnd;
Start : Cardinal;
Found : Boolean;
begin
{without list entry there is no text to speak}
if (strEspeak.Count = 0) or NextEspeakProcessing then
Exit;
{processing started}
NextEspeakProcessing := True;
try
{get the next text from TStrings (pipeline) and delete it there}
OutputText := strEspeak[0];
strEspeak.Delete(0);
{empty string means nothing to do}
if (OutputText = '') then
begin
{processing done and leave}
NextEspeakProcessing := False;
Exit;
end;
{does eSpeak still exist?}
if FileExists(fnmEspeak.FileName) then
begin
{if eSpeak is still running then stop it}
GetClassName(EspeakHandle, buff, SizeOf(buff));
if (buff = CMD_CLASS) then
begin
{stop previously started eSpeak so we have always only one instance}
repeat
PostMessage(EspeakHandle, $10, 0, 0);
until GetClassName(EspeakHandle, buff, SizeOf(buff)) = 0;
end;
{=== HERE we start eSpeak and pass all informations to it ===}
ExecuteFile(frmEspeak.fnmEspeak.Text,LANGUAGE + // ExecuteFile from the unit FMXUtils
edtEspeakModify.Text + OutputText + '"','',SW_Hide);
{on principle we are ready BUT we need the eSpeak handle. The following part is to find the handle}
Found := False;
Start := GetTickCount;
{unfortunately it takes some time to start eSpeak , so we have to wait a bit until we get the handle}
while (GetTickCount - Start <= 10000) do // max. 10 seconds to find the handle
begin
{go thru the list of windows and find eSpeak}
Wnd := GetWindow(Handle, gw_HWndFirst);
while Wnd <> 0 do
begin
if (Wnd <> Handle) and
not IsWindowVisible(Wnd) and // started hidden: SW_Hide
(GetWindow(Wnd, gw_Owner) = 0) and // top-level windows
(GetWindowText(Wnd, buff, SizeOf(buff)) <> 0) then // titel from the window
begin
if (Pos(ESPEAK_PROGRAM, LowerCase(buff)) > 0) then // we find the program name in the titel of cmd.exe
begin
GetClassName(Wnd, buff, SizeOf(buff));
if (Pos(CMD_CLASS, buff) > 0) then // the class of cmd.exe is ConsoleWindowClass
begin
EspeakHandle := Wnd; // this is the handle we were looking for :-)
Found := True;
Break; // so we are happy here and do nothing further
end;
end;
end;
{step to next window}
Wnd := GetWindow(Wnd, gw_hWndNext);
end;
{if the handle was found then we needn't wait 10 seconds}
if Found then
Break;
{do something useful}
Application.ProcessMessages;
{give cmd.exe time to start eSpeak}
Sleep(100);
end;
end
else
{couldn't find espeak.exe}
FindEspeak;
finally
{processing is done}
NextEspeakProcessing := False;
end;
end;
{if the user change values in the GUI then this procedure is executed}
procedure TfrmEspeak.eSpeakModify(Sender: TObject);
begin
{voice}
case Round(edtVoice.Value) of
-4: edtEspeakModify.Text := '+f4';
-3: edtEspeakModify.Text := '+f3';
-2: edtEspeakModify.Text := '+f2';
-1: edtEspeakModify.Text := '+f1';
0: edtEspeakModify.Text := '';
1: edtEspeakModify.Text := '+m1';
2: edtEspeakModify.Text := '+m2';
3: edtEspeakModify.Text := '+m3';
4: edtEspeakModify.Text := '+m4';
5: edtEspeakModify.Text := '+m5';
6: edtEspeakModify.Text := '+m6';
else edtEspeakModify.Text := '';
end;
{put everything together in the parameter string}
edtEspeakModify.Text := edtEspeakModify.Text + Format(ESPEAK_PARAMETER,[Round(edtEspeakVolume.Value),Round(edtWorteProMinute.Value)]);
end;
{the user try out eSpeak}
procedure TfrmEspeak.btnEspeakTestClick(Sender: TObject);
begin
{put the text into the pipeline of eSpeak}
eSpeak(edtEspeakTest.Text);
end;
{that's it}
end.