(*************************************************************************** * 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.