diff --git a/README.md b/README.md
index 999af50..9600e8a 100644
--- a/README.md
+++ b/README.md
@@ -18,3 +18,14 @@ Here you will find sample code and other tidbits from Grijjy's [Just Add Code](h
* [Custom Managed Records for Smart Pointers](CustomManagedRecords/SmartPointers), as discussed in this [blog post](https://blog.grijjy.com/2020/08/12/custom-managed-records-for-smart-pointers/).
* [Introduction to Shader Programming](GpuProgramming), as discussed in this [blog post](https://blog.grijjy.com/2021/01/14/shader-programming/).
+# changed/expanded in this fork by oMAR - mar20
+ Added features to *TextToSpeech* - JustAddCode/TextToSpeech
+ - selection of native language ( use phone OS settings )
+ - show list of available voices
+ - 2 person dialog talker ( male and female - TV journal style )
+ - set male and female voice languages ( see Example proj )
+
+Example app was updated to include the new features
+
+tested w/ D10.3.3
+
diff --git a/TextToSpeech/Example/FMain.fmx b/TextToSpeech/Example/FMain.fmx
index ef0f7ee..d3af06f 100644
--- a/TextToSpeech/Example/FMain.fmx
+++ b/TextToSpeech/Example/FMain.fmx
@@ -1,109 +1,243 @@
-object FormMain: TFormMain
- Left = 0
- Top = 0
- BorderIcons = [biSystemMenu, biMinimize]
- BorderStyle = Single
- Caption = 'Text-to-Speech'
- ClientHeight = 480
- ClientWidth = 320
- Padding.Left = 8.000000000000000000
- Padding.Top = 8.000000000000000000
- Padding.Right = 8.000000000000000000
- Padding.Bottom = 8.000000000000000000
- Position = ScreenCenter
- FormFactor.Width = 320
- FormFactor.Height = 480
- FormFactor.Devices = [Desktop]
- OnCreate = FormCreate
- DesignerMasterStyle = 2
- object Memo: TMemo
- Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
- DataDetectorTypes = []
- Lines.Strings = (
- 'The Quick Brown Fox Jumps Over The Lazy Dog')
- Align = Top
- Position.X = 8.000000000000000000
- Position.Y = 8.000000000000000000
- Size.Width = 304.000000000000000000
- Size.Height = 56.000000000000000000
- Size.PlatformDefault = False
- TabOrder = 0
- Viewport.Width = 296.000000000000000000
- Viewport.Height = 48.000000000000000000
- end
- object MemoLog: TMemo
- Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
- DataDetectorTypes = []
- ReadOnly = True
- Align = Client
- Margins.Top = 8.000000000000000000
- Size.Width = 304.000000000000000000
- Size.Height = 348.000000000000000000
- Size.PlatformDefault = False
- TabOrder = 1
- Viewport.Width = 296.000000000000000000
- Viewport.Height = 340.000000000000000000
- end
- object GridPanelLayout2: TGridPanelLayout
- Align = Top
- Margins.Top = 8.000000000000000000
- Position.X = 8.000000000000000000
- Position.Y = 72.000000000000000000
- Size.Width = 304.000000000000000000
- Size.Height = 44.000000000000000000
- Size.PlatformDefault = False
- TabOrder = 2
- ColumnCollection = <
- item
- Value = 50.000000000000010000
- end
- item
- Value = 49.999999999999990000
- end>
- ControlCollection = <
- item
- Column = 0
- Control = ButtonSpeak
- Row = 0
- end
- item
- Column = 1
- Control = ButtonStop
- Row = 0
- end>
- RowCollection = <
- item
- Value = 100.000000000000000000
- end
- item
- SizeStyle = Auto
- end>
- object ButtonSpeak: TButton
- Align = Top
- Enabled = False
- Margins.Top = 8.000000000000000000
- Position.Y = 8.000000000000000000
- Size.Width = 152.000000000000000000
- Size.Height = 29.000000000000000000
- Size.PlatformDefault = False
- StyleLookup = 'toolbuttonleft'
- TabOrder = 2
- Text = 'Speak'
- OnClick = ButtonSpeakClick
- end
- object ButtonStop: TButton
- Align = Top
- Enabled = False
- Margins.Top = 8.000000000000000000
- Position.X = 152.000000000000000000
- Position.Y = 8.000000000000000000
- Size.Width = 152.000000000000000000
- Size.Height = 29.000000000000000000
- Size.PlatformDefault = False
- StyleLookup = 'toolbuttonright'
- TabOrder = 1
- Text = 'Stop'
- OnClick = ButtonStopClick
- end
- end
-end
+object FormMain: TFormMain
+ Left = 0
+ Top = 0
+ BorderIcons = [biSystemMenu, biMinimize]
+ BorderStyle = Single
+ Caption = 'Text-to-Speech'
+ ClientHeight = 609
+ ClientWidth = 353
+ Padding.Left = 8.000000000000000000
+ Padding.Top = 8.000000000000000000
+ Padding.Right = 8.000000000000000000
+ Padding.Bottom = 8.000000000000000000
+ Position = ScreenCenter
+ FormFactor.Width = 320
+ FormFactor.Height = 480
+ FormFactor.Devices = [Desktop]
+ OnCreate = FormCreate
+ OnDestroy = FormDestroy
+ DesignerMasterStyle = 2
+ object Memo: TMemo
+ Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
+ DataDetectorTypes = []
+ Lines.Strings = (
+ 'Por mim se vai '#224' cidade dolente,'
+ 'por mim se vai '#224' eterna dor,'
+ 'por mim se vai entre a perdida gente.'
+ ''
+ 'Justi'#231'a moveu o meu alto feitor;'
+ 'fez-me a divina potestade,'
+ 'a suma sapi'#234'ncia e o primeiro amor.'
+ ''
+ 'Antes de mim n'#227'o foram coisas criadas'
+ 'sen'#227'o eternas, e eu eterna duro.'
+ 'Deixai toda esperan'#231'a, v'#243's que entrais.')
+ Align = Top
+ Position.X = 8.000000000000000000
+ Position.Y = 8.000000000000000000
+ Size.Width = 337.000000000000000000
+ Size.Height = 241.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 0
+ Viewport.Width = 329.000000000000000000
+ Viewport.Height = 233.000000000000000000
+ end
+ object MemoLog: TMemo
+ Touch.InteractiveGestures = [Pan, LongTap, DoubleTap]
+ DataDetectorTypes = []
+ ReadOnly = True
+ Align = Client
+ Margins.Top = 8.000000000000000000
+ Size.Width = 337.000000000000000000
+ Size.Height = 216.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 1
+ Viewport.Width = 329.000000000000000000
+ Viewport.Height = 208.000000000000000000
+ end
+ object GridPanelLayout2: TGridPanelLayout
+ Align = Top
+ Margins.Top = 8.000000000000000000
+ Position.X = 8.000000000000000000
+ Position.Y = 257.000000000000000000
+ Size.Width = 337.000000000000000000
+ Size.Height = 120.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 2
+ ColumnCollection = <
+ item
+ Value = 27.743880792792890000
+ end
+ item
+ Value = 26.057342538116000000
+ end
+ item
+ Value = 24.021623783121990000
+ end
+ item
+ Value = 22.177152885969120000
+ end>
+ ControlCollection = <
+ item
+ Column = 0
+ Control = ButtonSpeak
+ Row = 0
+ end
+ item
+ Column = 1
+ Control = ButtonStop
+ Row = 0
+ end
+ item
+ Column = 2
+ Control = btnListVoices
+ Row = 0
+ end
+ item
+ Column = 3
+ Control = btnClearLog
+ Row = 0
+ end
+ item
+ Column = 0
+ Control = Label1
+ Row = 1
+ end
+ item
+ Column = 1
+ Control = edFemaleVoiceLang
+ Row = 1
+ end
+ item
+ Column = 2
+ Control = edMaleVoiceLang
+ Row = 1
+ end
+ item
+ Column = 3
+ Control = btnSetVoice
+ Row = 1
+ end>
+ RowCollection = <
+ item
+ Value = 32.809194744060560000
+ end
+ item
+ Value = 36.733478003726720000
+ end
+ item
+ SizeStyle = Auto
+ Value = 30.000000000000000000
+ end
+ item
+ Value = 30.457327252212720000
+ end>
+ object ButtonSpeak: TButton
+ Align = Top
+ Enabled = False
+ StyledSettings = [Family, Style, FontColor]
+ Margins.Top = 8.000000000000000000
+ Position.Y = 8.000000000000000000
+ Size.Width = 93.496879577636720000
+ Size.Height = 29.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 4
+ Text = 'Speak'
+ TextSettings.Font.Size = 14.000000000000000000
+ OnClick = ButtonSpeakClick
+ end
+ object ButtonStop: TButton
+ Align = Top
+ Enabled = False
+ StyledSettings = [Family, Style, FontColor]
+ Margins.Top = 8.000000000000000000
+ Position.X = 93.496879577636720000
+ Position.Y = 8.000000000000000000
+ Size.Width = 87.813240051269530000
+ Size.Height = 29.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 3
+ Text = 'Stop'
+ TextSettings.Font.Size = 14.000000000000000000
+ OnClick = ButtonStopClick
+ end
+ object btnListVoices: TButton
+ Align = Top
+ StyledSettings = [Family, Style, FontColor]
+ Margins.Top = 8.000000000000000000
+ Position.X = 181.310119628906300000
+ Position.Y = 8.000000000000000000
+ Size.Width = 80.952880859375000000
+ Size.Height = 29.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 2
+ Text = 'List voices'
+ TextSettings.Font.Size = 10.000000000000000000
+ OnClick = btnListVoicesClick
+ end
+ object btnClearLog: TButton
+ Align = Top
+ StyledSettings = [Family, Style, FontColor]
+ Margins.Top = 8.000000000000000000
+ Position.X = 262.263000488281300000
+ Position.Y = 8.000000000000000000
+ Size.Width = 74.736999511718750000
+ Size.Height = 29.000000000000000000
+ Size.PlatformDefault = False
+ TabOrder = 1
+ Text = 'Clear log'
+ TextSettings.Font.Size = 10.000000000000000000
+ OnClick = btnClearLogClick
+ end
+ object Label1: TLabel
+ Anchors = []
+ StyledSettings = [Family, Style, FontColor]
+ Position.X = 8.045726776123047000
+ Position.Y = 49.911117553710940000
+ Size.Width = 77.405426025390630000
+ Size.Height = 23.000000000000000000
+ Size.PlatformDefault = False
+ TextSettings.HorzAlign = Trailing
+ Text = 'Voices:'
+ TabOrder = 5
+ end
+ object edFemaleVoiceLang: TEdit
+ Touch.InteractiveGestures = [LongTap, DoubleTap]
+ Anchors = []
+ StyleLookup = 'editstyle'
+ TabOrder = 6
+ Text = 'es-MX'
+ Position.X = 104.403503417968800000
+ Position.Y = 46.411117553710940000
+ Hint = 'female voice language ( format '#39'pt-BR'#39' )]'
+ Size.Width = 66.000000000000000000
+ Size.Height = 30.000000000000000000
+ Size.PlatformDefault = False
+ end
+ object edMaleVoiceLang: TEdit
+ Touch.InteractiveGestures = [LongTap, DoubleTap]
+ Anchors = []
+ StyleLookup = 'editstyle'
+ TabOrder = 7
+ Text = 'pt-BR'
+ Position.X = 193.286560058593800000
+ Position.Y = 46.411117553710940000
+ Hint = 'male voice language'
+ Size.Width = 57.000000000000000000
+ Size.Height = 30.000000000000000000
+ Size.PlatformDefault = False
+ end
+ object btnSetVoice: TButton
+ Anchors = []
+ Position.X = 275.631530761718800000
+ Position.Y = 39.411117553710940000
+ Size.Width = 47.999969482421880000
+ Size.Height = 44.000000000000000000
+ Size.PlatformDefault = False
+ StyleLookup = 'donetoolbutton'
+ TabOrder = 8
+ Text = 'ok'
+ OnClick = btnSetVoiceClick
+ end
+ end
+end
diff --git a/TextToSpeech/Example/FMain.pas b/TextToSpeech/Example/FMain.pas
index b2a18b9..30ee231 100644
--- a/TextToSpeech/Example/FMain.pas
+++ b/TextToSpeech/Example/FMain.pas
@@ -1,101 +1,189 @@
-unit FMain;
-
-interface
-
-uses
- System.SysUtils,
- System.Types,
- System.UITypes,
- System.Classes,
- System.Variants,
- FMX.Types,
- FMX.Controls,
- FMX.Forms,
- FMX.Graphics,
- FMX.Dialogs,
- FMX.StdCtrls,
- FMX.Controls.Presentation,
- FMX.ScrollBox,
- FMX.Memo,
- FMX.ExtCtrls,
- FMX.Layouts,
- Grijjy.TextToSpeech;
-
-type
- TFormMain = class(TForm)
- Memo: TMemo;
- MemoLog: TMemo;
- GridPanelLayout2: TGridPanelLayout;
- ButtonSpeak: TButton;
- ButtonStop: TButton;
- procedure FormCreate(Sender: TObject);
- procedure ButtonSpeakClick(Sender: TObject);
- procedure ButtonStopClick(Sender: TObject);
- private
- { Private declarations }
- FTextToSpeech: IgoTextToSpeech;
- procedure Log(const AMsg: String);
- procedure TextToSpeechAvailable(Sender: TObject);
- procedure TextToSpeechStarted(Sender: TObject);
- procedure TextToSpeechFinished(Sender: TObject);
- procedure UpdateControls;
- public
- { Public declarations }
- end;
-
-var
- FormMain: TFormMain;
-
-implementation
-
-{$R *.fmx}
-
-procedure TFormMain.ButtonSpeakClick(Sender: TObject);
-begin
- if (not FTextToSpeech.Speak(Memo.Text)) then
- Log('Unable to speak text');
-end;
-
-procedure TFormMain.ButtonStopClick(Sender: TObject);
-begin
- FTextToSpeech.Stop;
-end;
-
-procedure TFormMain.FormCreate(Sender: TObject);
-begin
- FTextToSpeech := TgoTextToSpeech.Create;
- FTextToSpeech.OnAvailable := TextToSpeechAvailable;
- FTextToSpeech.OnSpeechStarted := TextToSpeechStarted;
- FTextToSpeech.OnSpeechFinished := TextToSpeechFinished;
-end;
-
-procedure TFormMain.Log(const AMsg: String);
-begin
- MemoLog.Lines.Add(AMsg);
-end;
-
-procedure TFormMain.TextToSpeechAvailable(Sender: TObject);
-begin
- Log('Text-to-Speech engine is available');
- UpdateControls;
-end;
-
-procedure TFormMain.TextToSpeechFinished(Sender: TObject);
-begin
- Log('Speech finished');
- UpdateControls;
-end;
-
-procedure TFormMain.TextToSpeechStarted(Sender: TObject);
-begin
- Log('Speech started');
- UpdateControls;
-end;
-
-procedure TFormMain.UpdateControls;
-begin
- ButtonSpeak.Enabled := (not FTextToSpeech.IsSpeaking);
- ButtonStop.Enabled := (not ButtonSpeak.Enabled);
-end;
-
-end.
+unit FMain;
+
+interface
+
+uses
+ System.SysUtils,
+ System.Types,
+ System.UITypes,
+ System.Classes,
+ System.Variants,
+ FMX.Types,
+ FMX.Controls,
+ FMX.Forms,
+ FMX.Graphics,
+ FMX.Dialogs,
+ FMX.StdCtrls,
+ FMX.Controls.Presentation,
+ FMX.ScrollBox,
+ FMX.Memo,
+ FMX.ExtCtrls,
+ FMX.Layouts,
+ Grijjy.TextToSpeech, FMX.Edit;
+
+type
+ TFormMain = class(TForm)
+ Memo: TMemo;
+ MemoLog: TMemo;
+ GridPanelLayout2: TGridPanelLayout;
+ ButtonSpeak: TButton;
+ ButtonStop: TButton;
+ btnListVoices: TButton;
+ btnClearLog: TButton;
+ Label1: TLabel;
+ edFemaleVoiceLang: TEdit;
+ btnSetVoice: TButton;
+ edMaleVoiceLang: TEdit;
+ procedure FormCreate(Sender: TObject);
+ procedure ButtonSpeakClick(Sender: TObject);
+ procedure ButtonStopClick(Sender: TObject);
+ procedure btnListVoicesClick(Sender: TObject);
+ procedure FormDestroy(Sender: TObject);
+ procedure btnClearLogClick(Sender: TObject);
+ procedure btnSetVoiceClick(Sender: TObject);
+ private
+ { Private declarations }
+ FTextToSpeech: IgoTextToSpeech;
+ // implemented a speech queue. If speaking, the string goes to the queue and waits for the terminated event
+ // - This is necessary for Android, which truncates the speech if another string is spoken
+ // - Gives more control on the speech queue
+
+ fSpeechQueue:TStringList; // local speech queue
+
+ procedure Log(const AMsg: String);
+ procedure TextToSpeechAvailable(Sender: TObject);
+ procedure TextToSpeechStarted(Sender: TObject);
+ procedure TextToSpeechFinished(Sender: TObject);
+ procedure UpdateControls;
+ public
+ { Public declarations }
+ end;
+
+var
+ FormMain: TFormMain;
+
+implementation
+
+{$R *.fmx}
+
+procedure TFormMain.btnClearLogClick(Sender: TObject);
+begin
+ MemoLog.Lines.Clear;
+end;
+
+procedure TFormMain.btnListVoicesClick(Sender: TObject);
+var SL:TStringList;
+begin
+ SL := TStringList.Create;
+ if FTextToSpeech.getVoices(SL) then
+ MemoLog.Lines.Assign(SL)
+ else MemoLog.Lines.Add('error loading voices');
+ SL.Free;
+end;
+
+procedure TFormMain.btnSetVoiceClick(Sender: TObject);
+var aMV,aFV:String;
+begin
+ // set male and female voices language
+ aMV := Trim( edMaleVoiceLang.Text ); // 'pt-BR' 'en-US' 'es-MX' ...
+ aFV := Trim( edFemaleVoiceLang.Text );
+
+ fTextToSpeech.SetVoice( aMV, aFV );
+end;
+
+procedure TFormMain.ButtonSpeakClick(Sender: TObject); // <-- Do speak
+var i:integer; s:String;
+begin
+ for i := 0 to Memo.Lines.Count-1 do // speak one line at a time ( for dialogs )
+ begin
+ s := Memo.Lines[i];
+ if Trim(S)='' then continue;
+
+ if not FTextToSpeech.IsSpeaking then // avoid using the OS queue (some don't have it)
+ begin
+ if FTextToSpeech.Speak(s) then // <-- do speak
+ begin
+ // great
+ end
+ else begin
+ // add to queue ?
+ Log('Unable to speak text');
+ exit;
+ end;
+ end
+ else begin //already speaking. Add s to queue
+ fSpeechQueue.Add(s);
+ end;
+ end;
+end;
+
+procedure TFormMain.ButtonStopClick(Sender: TObject);
+begin
+ FTextToSpeech.Stop;
+end;
+
+procedure TFormMain.FormCreate(Sender: TObject);
+begin
+ FTextToSpeech := TgoTextToSpeech.Create;
+
+ FTextToSpeech.OnAvailable := TextToSpeechAvailable;
+ FTextToSpeech.OnSpeechStarted := TextToSpeechStarted;
+ FTextToSpeech.OnSpeechFinished := TextToSpeechFinished;
+
+ fSpeechQueue := TStringList.Create; // local speech queue
+
+ MemoLog.Lines.Add( 'default language= '+NativeSpeechLanguage ); // show OS languege settings
+end;
+
+procedure TFormMain.FormDestroy(Sender: TObject);
+begin
+ fSpeechQueue.Free;
+end;
+
+procedure TFormMain.Log(const AMsg: String);
+begin
+ MemoLog.Lines.Add(AMsg);
+end;
+
+procedure TFormMain.TextToSpeechAvailable(Sender: TObject); // speech callback
+begin
+ Log('Text-to-Speech engine is available');
+ UpdateControls;
+end;
+
+procedure TFormMain.TextToSpeechFinished(Sender: TObject); // speech callback
+var s:String;
+begin
+ // retrieve speech s from queue, if any
+ if (not FTextToSpeech.IsSpeaking) and (fSpeechQueue.Count>0) then // avoid using the OS queue (some don't have it)
+ begin
+ s := fSpeechQueue.Strings[0];
+ fSpeechQueue.Delete(0);
+
+ if FTextToSpeech.Speak(s) then // <-- do speak
+ begin // great
+ end
+ else begin //error ?
+ // add to queue ?
+ Log('Unable to speak text');
+ exit;
+ end;
+ end;
+
+ Log('Speech finished');
+ UpdateControls;
+end;
+
+procedure TFormMain.TextToSpeechStarted(Sender: TObject); // speech callback
+begin
+ Log('Speech started');
+ UpdateControls;
+end;
+
+procedure TFormMain.UpdateControls; // upd ui with speech engine state
+begin
+ ButtonSpeak.Enabled := (not FTextToSpeech.IsSpeaking);
+ ButtonStop.Enabled := (not ButtonSpeak.Enabled);
+end;
+
+end.
diff --git a/TextToSpeech/Example/TextToSpeech.dpr b/TextToSpeech/Example/TextToSpeech.dpr
index cf40d55..a1fedc5 100644
--- a/TextToSpeech/Example/TextToSpeech.dpr
+++ b/TextToSpeech/Example/TextToSpeech.dpr
@@ -1,14 +1,14 @@
-program TextToSpeech;
-
-uses
- System.StartUpCopy,
- FMX.Forms,
- FMain in 'FMain.pas' {FormMain};
-
-{$R *.res}
-
-begin
- Application.Initialize;
- Application.CreateForm(TFormMain, FormMain);
- Application.Run;
-end.
+program TextToSpeech;
+
+uses
+ System.StartUpCopy,
+ FMX.Forms,
+ FMain in 'FMain.pas' {FormMain};
+
+{$R *.res}
+
+begin
+ Application.Initialize;
+ Application.CreateForm(TFormMain, FormMain);
+ Application.Run;
+end.
diff --git a/TextToSpeech/Example/TextToSpeech.dproj b/TextToSpeech/Example/TextToSpeech.dproj
index f113970..8c5e121 100644
--- a/TextToSpeech/Example/TextToSpeech.dproj
+++ b/TextToSpeech/Example/TextToSpeech.dproj
@@ -1,1701 +1,1864 @@
-
-
- {CBF74BA2-5DE4-4E50-9A7E-689A72EA93BF}
- 18.2
- FMX
- TextToSpeech.dpr
- True
- Debug
- Win32
- 1119
- Application
-
-
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Base
- true
-
-
- true
- Cfg_1
- true
- true
-
-
- true
- Cfg_1
- true
- true
-
-
- true
- Cfg_1
- true
- true
-
-
- true
- Cfg_1
- true
- true
-
-
- true
- Cfg_1
- true
- true
-
-
- true
- Base
- true
-
-
- true
- Cfg_2
- true
- true
-
-
- true
- Cfg_2
- true
- true
-
-
- ..\;$(DCC_UnitSearchPath)
- true
- true
- System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
- true
- $(BDS)\bin\delphi_PROJECTICNS.icns
- true
- true
- $(BDS)\bin\delphi_PROJECTICON.ico
- TextToSpeech
- true
- true
- true
- true
- true
- .\$(Platform)\$(Config)
- .\$(Platform)\$(Config)
- false
- false
- false
- false
- false
-
-
- $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png
- $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png
- $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png
- android-support-v4.dex.jar;apk-expansion.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;$(DCC_UsePackage)
- $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png
- true
- $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png
- $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png
- $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png
- package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=
- $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png
- $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png
- Debug
-
-
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
- true
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
- $(MSBuildProjectName)
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
- CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
- iPhoneAndiPad
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
- Debug
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
-
-
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
- true
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
- $(MSBuildProjectName)
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
- CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
- iPhoneAndiPad
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
- Debug
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
-
-
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
- true
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
- CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
- iPhoneAndiPad
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
- $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
- $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
-
-
- true
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;FireDACODBCDriver;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
- CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts
- Debug
-
-
- $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
- $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
- Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
- true
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;vclribbon;dbxcds;VclSmp;soapserver;adortl;FireDACODBCDriver;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;Grijjy.Package.RTL;FireDACCommonDriver;Grijjy.Package.FMX;inet;fmxase;$(DCC_UsePackage)
- 1033
- $(BDS)\bin\default_app.manifest
- CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName)
-
-
- $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
- $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
- Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
- true
- DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;vclribbon;dbxcds;VclSmp;soapserver;adortl;FireDACODBCDriver;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
- 1033
- $(BDS)\bin\default_app.manifest
- CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName)
-
-
- DEBUG;$(DCC_Define)
- true
- false
- true
- true
- true
-
-
- 1
- 1
-
-
- true
- $(MSBuildProjectName)
- 1
- iPhoneAndiPad
-
-
- true
- $(MSBuildProjectName)
- 1
- iPhoneAndiPad
-
-
- CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
- Debug
- true
- 1033
- true
- true
- false
-
-
- Debug
- true
- true
-
-
- false
- RELEASE;$(DCC_Define)
- 0
- 0
-
-
- Debug
- true
- true
-
-
- Debug
- true
- true
-
-
-
- MainSource
-
-
-
- fmx
-
-
- Cfg_2
- Base
-
-
- Base
-
-
- Cfg_1
- Base
-
-
-
- Delphi.Personality.12
- Application
-
-
-
- TextToSpeech.dpr
-
-
- Microsoft Office 2000 Sample Automation Server Wrapper Components
- Microsoft Office XP Sample Automation Server Wrapper Components
-
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-568h@2x.png
- true
-
-
-
-
- Default-568h@2x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- ResourceRules.plist
- true
-
-
-
-
- Default-Landscape-736h@3x.png
- true
-
-
-
-
- Default-Portrait@2x~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-667h@2x.png
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- Default-Portrait@2x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default@2x.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait@2x.png
- true
-
-
-
-
- Default.png
- true
-
-
-
-
- TextToSpeech.icns
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- true
-
-
-
-
- Default.png
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape.png
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- Default-667h@2x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- Default-667h@2x.png
- true
-
-
-
-
- Default~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- Info.plist
- true
-
-
-
-
- true
-
-
-
-
- Info.plist
- true
-
-
-
-
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait~ipad.png
- true
-
-
-
-
- Info.plist
- true
-
-
-
-
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- classes.dex
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-736h@3x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- Default@2x.png
- true
-
-
-
-
- true
-
-
-
-
- ResourceRules.plist
- true
-
-
-
-
- true
-
-
-
-
- Default-568h@2x.png
- true
-
-
-
-
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape@2x.png
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-736h@3x.png
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- TextToSpeech.exe
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- true
-
-
-
-
- Default@2x.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait~ipad.png
- true
-
-
-
-
- Default.png
- true
-
-
-
-
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait@2x.png
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- Default-Landscape-736h@3x.png
- true
-
-
-
-
- Default-Landscape@2x.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait@2x~ipad.png
- true
-
-
-
-
- Default-Landscape@2x~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- classes.dex
- true
-
-
-
-
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- Default-Landscape@2x~ipad.png
- true
-
-
-
-
- Default-Portrait~ipad.png
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- Info.plist
- true
-
-
-
-
- Default~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- Default-736h@3x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape-736h@3x.png
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- ic_launcher.png
- true
-
-
-
-
- splash_image.png
- true
-
-
-
-
- Default-Landscape.png
- true
-
-
-
-
- Default-Landscape~ipad.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape.png
- true
-
-
-
-
- true
-
-
-
-
- Default-Landscape@2x.png
- true
-
-
-
-
- true
-
-
-
-
- ResourceRules.plist
- true
-
-
-
-
- Default-Landscape@2x~ipad.png
- true
-
-
-
-
- libTextToSpeech.so
- true
-
-
-
-
- true
-
-
-
-
- true
-
-
-
-
- Default-Portrait@2x~ipad.png
- true
-
-
-
-
- TextToSpeech
- true
-
-
-
-
- Default-Landscape~ipad.png
- true
-
-
-
-
- 0
- .dll;.bpl
-
-
- 1
- .dylib
-
-
- Contents\MacOS
- 1
- .dylib
-
-
- 1
- .dylib
-
-
- 1
- .dylib
-
-
-
-
- Contents\Resources
- 1
-
-
-
-
- classes
- 1
-
-
-
-
- Contents\MacOS
- 0
-
-
- 1
-
-
- Contents\MacOS
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- res\drawable-xxhdpi
- 1
-
-
-
-
- library\lib\mips
- 1
-
-
-
-
- 0
-
-
- 1
-
-
- Contents\MacOS
- 1
-
-
- 1
-
-
- library\lib\armeabi-v7a
- 1
-
-
- 1
-
-
-
-
- 0
-
-
- Contents\MacOS
- 1
- .framework
-
-
-
-
- 1
-
-
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
- 1
-
-
- ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
- 1
-
-
-
-
- library\lib\x86
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- library\lib\armeabi
- 1
-
-
-
-
- 0
-
-
- 1
-
-
- Contents\MacOS
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- res\drawable-normal
- 1
-
-
-
-
- res\drawable-xhdpi
- 1
-
-
-
-
- res\drawable-large
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- Assets
- 1
-
-
- Assets
- 1
-
-
-
-
- ../
- 1
-
-
- ../
- 1
-
-
-
-
- res\drawable-hdpi
- 1
-
-
-
-
- library\lib\armeabi-v7a
- 1
-
-
-
-
- Contents
- 1
-
-
-
-
- ../
- 1
-
-
-
-
- Assets
- 1
-
-
- Assets
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- res\values
- 1
-
-
-
-
- res\drawable-small
- 1
-
-
-
-
- res\drawable
- 1
-
-
-
-
- 1
-
-
- 1
-
-
- 1
-
-
-
-
- 1
-
-
-
-
- res\drawable
- 1
-
-
-
-
- 0
-
-
- 0
-
-
- Contents\Resources\StartUp\
- 0
-
-
- 0
-
-
- 0
-
-
- 0
-
-
-
-
- library\lib\armeabi-v7a
- 1
-
-
-
-
- 0
- .bpl
-
-
- 1
- .dylib
-
-
- Contents\MacOS
- 1
- .dylib
-
-
- 1
- .dylib
-
-
- 1
- .dylib
-
-
-
-
- res\drawable-mdpi
- 1
-
-
-
-
- res\drawable-xlarge
- 1
-
-
-
-
- res\drawable-ldpi
- 1
-
-
-
-
- 1
-
-
- 1
-
-
-
-
-
-
-
-
-
-
-
-
- True
- True
- True
- True
- True
- True
- True
-
-
- 12
-
-
-
-
-
+
+
+ {CBF74BA2-5DE4-4E50-9A7E-689A72EA93BF}
+ 18.8
+ FMX
+ TextToSpeech.dpr
+ True
+ Debug
+ iOSDevice64
+ 37983
+ Application
+
+
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Cfg_1
+ true
+ true
+
+
+ true
+ Base
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ true
+ Cfg_2
+ true
+ true
+
+
+ ..\;$(DCC_UnitSearchPath)
+ true
+ true
+ System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace)
+ true
+ $(BDS)\bin\delphi_PROJECTICNS.icns
+ true
+ true
+ $(BDS)\bin\delphi_PROJECTICON.ico
+ TextToSpeech
+ true
+ true
+ true
+ true
+ true
+ .\$(Platform)\$(Config)
+ .\$(Platform)\$(Config)
+ false
+ false
+ false
+ false
+ false
+
+
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png
+ android-support-v4.dex.jar;apk-expansion.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;$(DCC_UsePackage)
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png
+ true
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png
+ package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png
+ Debug
+ $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png
+ $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png
+ $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png
+ $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png
+ $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png
+
+
+ package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=
+ Debug
+ true
+ true
+ Base
+ true
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png
+ android-support-v4.dex.jar;apk-expansion.dex.jar;cloud-messaging.dex.jar;fmx.dex.jar;google-analytics-v2.dex.jar;google-play-billing.dex.jar;google-play-licensing.dex.jar;google-play-services.dex.jar
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;$(DCC_UsePackage);$(DCC_UsePackage)
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png
+ $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png
+ $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png
+
+
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
+ true
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
+ $(MSBuildProjectName)
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
+ iPhoneAndiPad
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
+ Debug
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png
+
+
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
+ true
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
+ $(MSBuildProjectName)
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
+ iPhoneAndiPad
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
+ Debug
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png
+
+
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_60x60.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_87x87.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_29x29.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_57x57.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_50x50.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_750x1334.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x748.png
+ true
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;xmlrtl;soapmidas;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;bindengine;CloudService;dsnapxml;dbrtl;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1004.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1496.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_320x480.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x960.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2048.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2048x1536.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1536x2008.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_144x144.png
+ CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;CFBundleResourceSpecification=ResourceRules.plist;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;FMLocalNotificationPermission=false;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSCameraUsageDescription=The reason for accessing the camera;CFBundleShortVersionString=1.0.0;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSFaceIDUsageDescription=The reason for accessing the face id;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_768x1024.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_640x1136.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_29x29.png
+ iPhoneAndiPad
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_40x40.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_1024x768.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_100x100.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_76x76.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_114x114.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2208x1242.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_58x58.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2208.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_72x72.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1125x2436.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2436x1125.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_828x1792.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1136x640.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1242x2688.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1334x750.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_1792x828.png
+ $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2688x1242.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2224.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_1668x2388.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImagePortrait_2048x2732.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2224x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2388x1668.png
+ $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageLandscape_2732x2048.png
+
+
+ true
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;FireDACODBCDriver;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user
+ Debug
+
+
+ CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSContactsUsageDescription=The reason for accessing the contacts;CFBundleShortVersionString=1.0.0;NSLocationUsageDescription=The reason for accessing the location information of the user
+ Debug
+ true
+ true
+ Base
+ true
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;IndyIPServer;IndySystem;tethering;fmxFireDAC;FireDAC;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;rtl;DbxClientDriver;CustomIPTransport;dbexpress;IndyCore;bindcomp;dsnap;FireDACCommon;IndyIPClient;RESTBackendComponents;dbxcds;soapserver;FireDACODBCDriver;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage);$(DCC_UsePackage)
+
+
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace)
+ true
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;svnui;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;svn;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;vclribbon;dbxcds;VclSmp;soapserver;adortl;FireDACODBCDriver;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;Grijjy.Package.RTL;FireDACCommonDriver;Grijjy.Package.FMX;inet;fmxase;$(DCC_UsePackage)
+ 1033
+ $(BDS)\bin\default_app.manifest
+ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName)
+
+
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png
+ $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png
+ Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace)
+ true
+ DBXSqliteDriver;bindcompdbx;IndyIPCommon;RESTComponents;DBXInterBaseDriver;vcl;IndyIPServer;vclactnband;vclFireDAC;IndySystem;tethering;dsnapcon;FireDACADSDriver;FireDACMSAccDriver;fmxFireDAC;vclimg;FireDAC;vcltouch;vcldb;bindcompfmx;FireDACSqliteDriver;FireDACPgDriver;FireDACASADriver;inetdb;soaprtl;DbxCommonDriver;FireDACIBDriver;fmx;fmxdae;xmlrtl;soapmidas;fmxobj;vclwinx;rtl;DbxClientDriver;CustomIPTransport;vcldsnap;dbexpress;IndyCore;vclx;bindcomp;appanalytics;dsnap;FireDACCommon;IndyIPClient;bindcompvcl;RESTBackendComponents;VCLRESTComponents;vclribbon;dbxcds;VclSmp;soapserver;adortl;FireDACODBCDriver;vclie;bindengine;DBXMySQLDriver;CloudService;dsnapxml;FireDACMySQLDriver;dbrtl;inetdbxpress;IndyProtocols;FireDACCommonDriver;inet;fmxase;$(DCC_UsePackage)
+ 1033
+ $(BDS)\bin\default_app.manifest
+ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(ModuleName);FileDescription=$(ModuleName);ProductName=$(ModuleName)
+
+
+ DEBUG;$(DCC_Define)
+ true
+ false
+ true
+ true
+ true
+
+
+ 1
+ 1
+
+
+ true
+ Cfg_1
+ true
+ 1
+ 1
+ \dpr4\JustAddCode-master\TextToSpeech;$(DCC_UnitSearchPath)
+ #000000
+
+
+ true
+ $(MSBuildProjectName)
+ 1
+ iPhoneAndiPad
+
+
+ true
+ $(MSBuildProjectName)
+ 1
+ iPhoneAndiPad
+
+
+ CompanyName=;FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProductVersion=1.0.0.0;Comments=;ProgramID=com.embarcadero.$(MSBuildProjectName);FileDescription=$(MSBuildProjectName);ProductName=$(MSBuildProjectName)
+ Debug
+ true
+ 1033
+ true
+ false
+ PerMonitor
+
+
+ Debug
+ true
+ PerMonitor
+
+
+ false
+ RELEASE;$(DCC_Define)
+ 0
+ 0
+
+
+ Debug
+ true
+ PerMonitor
+
+
+ Debug
+ true
+ PerMonitor
+
+
+
+ MainSource
+
+
+
+ fmx
+
+
+ Cfg_2
+ Base
+
+
+ Base
+
+
+ Cfg_1
+ Base
+
+
+
+ Delphi.Personality.12
+ Application
+
+
+
+ TextToSpeech.dpr
+
+
+ TurboPack LockBox 3 FMX designtime package
+ Microsoft Office 2000 Sample Automation Server Wrapper Components
+ Microsoft Office XP Sample Automation Server Wrapper Components
+
+
+
+
+
+ Default-Landscape~ipad.png
+ true
+
+
+
+
+
+ TextToSpeech
+ true
+
+
+
+
+
+
+ Default-Landscape-2048w-2732h@2x~ipad.png
+ true
+
+
+
+
+
+
+
+
+ Default-640w-1136h@2x.png
+ true
+
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+
+ Default-1668w-2388h@2x~ipad.png
+ true
+
+
+
+
+
+ ic_launcher.png
+ true
+
+
+
+
+
+ ic_launcher.png
+ true
+
+
+
+
+ Default-750w-1334h@2x.png
+ true
+
+
+
+
+ Default-Landscape-828w-1792h@2x.png
+ true
+
+
+
+
+
+
+
+
+
+ Default@2x.png
+ true
+
+
+
+
+
+ Default-1668w-2224h@2x~ipad.png
+ true
+
+
+
+
+ classes.dex
+ true
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+ Default.png
+ true
+
+
+
+
+ true
+
+
+
+
+ Default-2048w-2732h@2x~ipad.png
+ true
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+ Info.plist
+ true
+
+
+
+
+ ic_launcher.png
+ true
+
+
+
+
+ Default-Landscape-750w-1334h@2x.png
+ true
+
+
+
+
+
+
+
+ libTextToSpeech.so
+ true
+
+
+
+
+
+ splash_image.png
+ true
+
+
+
+
+
+ Default-Landscape-1668w-2224h@2x~ipad.png
+ true
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+
+
+ Default-828w-1792h@2x.png
+ true
+
+
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+
+
+
+ Default-1242w-2208h@3x.png
+ true
+
+
+
+
+
+
+ ic_launcher.png
+ true
+
+
+
+
+
+
+
+ Default-Landscape-640w-1136h@2x.png
+ true
+
+
+
+
+
+ true
+
+
+
+
+ splash_image.png
+ true
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+
+
+
+
+
+ Default-1242w-2688h@3x.png
+ true
+
+
+
+
+ libTextToSpeech.so
+ true
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+ Default-1536w-2048h@2x~ipad.png
+ true
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ splash_image.png
+ true
+
+
+
+
+ true
+
+
+
+
+ ic_launcher.png
+ true
+
+
+
+
+
+
+
+ splash_image.png
+ true
+
+
+
+
+ Default-Portrait~ipad.png
+ true
+
+
+
+
+
+
+ TextToSpeech
+ true
+
+
+
+
+
+ Default-Landscape-1125w-2436h@3x.png
+ true
+
+
+
+
+
+ true
+
+
+
+
+ libTextToSpeech.so
+ true
+
+
+
+
+ libTextToSpeech.so
+ true
+
+
+
+
+
+
+
+ true
+
+
+
+
+ Default-Landscape-1242w-2208h@3x.png
+ true
+
+
+
+
+ true
+
+
+
+
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+
+
+ Default-1125w-2436h@3x.png
+ true
+
+
+
+
+
+
+ true
+
+
+
+
+ true
+
+
+
+
+ Default-Landscape-1668w-2388h@2x~ipad.png
+ true
+
+
+
+
+ true
+
+
+
+
+ ResourceRules.plist
+ true
+
+
+
+
+ Default-Landscape-1536w-2048h@2x~ipad.png
+ true
+
+
+
+
+ true
+
+
+
+
+
+ styles.xml
+ true
+
+
+
+
+
+ Default-Landscape-1242w-2688h@3x.png
+ true
+
+
+
+
+
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ classes
+ 1
+
+
+ classes
+ 1
+
+
+
+
+ res\xml
+ 1
+
+
+ res\xml
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\armeabi
+ 1
+
+
+ library\lib\armeabi
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ library\lib\mips
+ 1
+
+
+ library\lib\mips
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\values-v21
+ 1
+
+
+ res\values-v21
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ res\drawable
+ 1
+
+
+ res\drawable
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-ldpi
+ 1
+
+
+ res\drawable-ldpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-mdpi
+ 1
+
+
+ res\drawable-mdpi
+ 1
+
+
+
+
+ res\drawable-hdpi
+ 1
+
+
+ res\drawable-hdpi
+ 1
+
+
+
+
+ res\drawable-xhdpi
+ 1
+
+
+ res\drawable-xhdpi
+ 1
+
+
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+ res\drawable-xxhdpi
+ 1
+
+
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+ res\drawable-xxxhdpi
+ 1
+
+
+
+
+ res\drawable-small
+ 1
+
+
+ res\drawable-small
+ 1
+
+
+
+
+ res\drawable-normal
+ 1
+
+
+ res\drawable-normal
+ 1
+
+
+
+
+ res\drawable-large
+ 1
+
+
+ res\drawable-large
+ 1
+
+
+
+
+ res\drawable-xlarge
+ 1
+
+
+ res\drawable-xlarge
+ 1
+
+
+
+
+ res\values
+ 1
+
+
+ res\values
+ 1
+
+
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ Contents\MacOS
+ 1
+ .framework
+
+
+ Contents\MacOS
+ 1
+ .framework
+
+
+ 0
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ 0
+ .dll;.bpl
+
+
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ Contents\MacOS
+ 1
+ .dylib
+
+
+ 0
+ .bpl
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ Contents\Resources\StartUp\
+ 0
+
+
+ Contents\Resources\StartUp\
+ 0
+
+
+ 0
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\
+ 1
+
+
+ ..\
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+
+
+ ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF
+ 1
+
+
+
+
+ ..\
+ 1
+
+
+ ..\
+ 1
+
+
+
+
+ Contents
+ 1
+
+
+ Contents
+ 1
+
+
+
+
+ Contents\Resources
+ 1
+
+
+ Contents\Resources
+ 1
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+ library\lib\arm64-v8a
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ Contents\MacOS
+ 1
+
+
+ 0
+
+
+
+
+ library\lib\armeabi-v7a
+ 1
+
+
+
+
+ 1
+
+
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+ Assets
+ 1
+
+
+ Assets
+ 1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+ True
+
+
+ 12
+
+
+
+
+
+
diff --git a/TextToSpeech/Example/TextToSpeech.res b/TextToSpeech/Example/TextToSpeech.res
index dafd683..36f26e2 100644
Binary files a/TextToSpeech/Example/TextToSpeech.res and b/TextToSpeech/Example/TextToSpeech.res differ
diff --git a/TextToSpeech/Example/readme.md b/TextToSpeech/Example/readme.md
new file mode 100644
index 0000000..8b1a606
--- /dev/null
+++ b/TextToSpeech/Example/readme.md
@@ -0,0 +1,18 @@
+# Sample app for JustAddCode/TextToSpeech
+
+changed in this fork by oMAR apr20
+
+- added - show list of voices.
+ on iOS there are 59 voices. For some languages there are male and female voices. Others have only one of them.
+ on Android you may have to download voices on text-to-speech settings.
+ If there are male and female voices available for the language, the program enters alternating mode ( one line for each gender )
+
+ - added - setVoice - param= 'pt-BR' or 'en-US', 'de-DE', 'fr-FR', 'it-IT', 'es-MX' ...
+ specify female and male voice laguages
+
+ The example includes a simple speech queue on a TStringList.
+
+
+
+
+
diff --git a/TextToSpeech/Grijjy.TextToSpeech.Android.pas b/TextToSpeech/Grijjy.TextToSpeech.Android.pas
index 0e15814..f79a70d 100644
--- a/TextToSpeech/Grijjy.TextToSpeech.Android.pas
+++ b/TextToSpeech/Grijjy.TextToSpeech.Android.pas
@@ -1,333 +1,534 @@
-unit Grijjy.TextToSpeech.Android;
-{< Text To Speech engine implementation for Android }
-
-interface
-
-uses
- Androidapi.JNIBridge,
- Androidapi.JNI.JavaTypes,
- {$IF RTLVersion >= 31}
- Androidapi.JNI.Speech,
- {$ELSE}
- Androidapi.JNI.GraphicsContentViewText,
- {$ENDIF}
- Grijjy.TextToSpeech.Base;
-
-{$IF RTLVersion < 31}
-{ Delphi 10 Seattle and ealier versions don't have imports for the Java
- Text-to-Speech classes. So we imported the parts we need ourselves using
- Java2OP. }
-
-type
- JTextToSpeech_OnInitListenerClass = interface(IJavaClass)
- ['{B01450B5-524A-4B99-95DC-9158B7B8CC15}']
- end;
-
- [JavaSignature('android/speech/tts/TextToSpeech$OnInitListener')]
- JTextToSpeech_OnInitListener = interface(IJavaInstance)
- ['{94CC537C-E958-4EA5-B613-1465AEF6014B}']
- procedure onInit(status: Integer); cdecl;
- end;
- TJTextToSpeech_OnInitListener = class(TJavaGenericImport) end;
-
-type
- JTextToSpeech_OnUtteranceCompletedListenerClass = interface(IJavaClass)
- ['{83D093B7-6FB6-46FE-A08E-1B0D25BDA841}']
- end;
-
- [JavaSignature('android/speech/tts/TextToSpeech$OnUtteranceCompletedListener')]
- JTextToSpeech_OnUtteranceCompletedListener = interface(IJavaInstance)
- ['{3EA0D21E-74E4-4204-A18F-2F68FE126E18}']
- procedure onUtteranceCompleted(utteranceId: JString); cdecl;
- end;
- TJTextToSpeech_OnUtteranceCompletedListener = class(TJavaGenericImport) end;
-
-type
- JUtteranceProgressListener = interface;
-
- JUtteranceProgressListenerClass = interface(JObjectClass)
- ['{9D335A6E-78BE-4060-B3C1-6028E603073D}']
- {class} function init: JUtteranceProgressListener; cdecl;
- end;
-
- [JavaSignature('android/speech/tts/UtteranceProgressListener')]
- JUtteranceProgressListener = interface(JObject)
- ['{75D1A7E1-86E7-47D6-B9EC-96F0D69DC535}']
- procedure onDone(utteranceId: JString); cdecl;
- procedure onError(utteranceId: JString); cdecl;
- procedure onStart(utteranceId: JString); cdecl;
- end;
- TJUtteranceProgressListener = class(TJavaGenericImport) end;
-
-type
- JTextToSpeech = interface;
-
- JTextToSpeechClass = interface(JObjectClass)
- ['{BE260883-0916-456E-B84C-6B237C8382DA}']
- {class} function _GetACTION_TTS_QUEUE_PROCESSING_COMPLETED: JString;
- {class} function _GetERROR: Integer;
- {class} function _GetLANG_AVAILABLE: Integer;
- {class} function _GetLANG_COUNTRY_AVAILABLE: Integer;
- {class} function _GetLANG_COUNTRY_VAR_AVAILABLE: Integer;
- {class} function _GetLANG_MISSING_DATA: Integer;
- {class} function _GetLANG_NOT_SUPPORTED: Integer;
- {class} function _GetQUEUE_ADD: Integer;
- {class} function _GetQUEUE_FLUSH: Integer;
- {class} function _GetSUCCESS: Integer;
- {class} function init(context: JContext; listener: JTextToSpeech_OnInitListener): JTextToSpeech; cdecl; overload;
- {class} function init(context: JContext; listener: JTextToSpeech_OnInitListener; engine: JString): JTextToSpeech; cdecl; overload;
-// {class} function getMaxSpeechInputLength: Integer; cdecl; { Requires Android 4.3 }
- {class} property ACTION_TTS_QUEUE_PROCESSING_COMPLETED: JString read _GetACTION_TTS_QUEUE_PROCESSING_COMPLETED;
- {class} property ERROR: Integer read _GetERROR;
- {class} property LANG_AVAILABLE: Integer read _GetLANG_AVAILABLE;
- {class} property LANG_COUNTRY_AVAILABLE: Integer read _GetLANG_COUNTRY_AVAILABLE;
- {class} property LANG_COUNTRY_VAR_AVAILABLE: Integer read _GetLANG_COUNTRY_VAR_AVAILABLE;
- {class} property LANG_MISSING_DATA: Integer read _GetLANG_MISSING_DATA;
- {class} property LANG_NOT_SUPPORTED: Integer read _GetLANG_NOT_SUPPORTED;
- {class} property QUEUE_ADD: Integer read _GetQUEUE_ADD;
- {class} property QUEUE_FLUSH: Integer read _GetQUEUE_FLUSH;
- {class} property SUCCESS: Integer read _GetSUCCESS;
- end;
-
- [JavaSignature('android/speech/tts/TextToSpeech')]
- JTextToSpeech = interface(JObject)
- ['{38B05C3C-B672-4FEC-849B-0CF4D89AA507}']
- function addEarcon(earcon: JString; packagename: JString; resourceId: Integer): Integer; cdecl; overload;
- function addEarcon(earcon: JString; filename: JString): Integer; cdecl; overload;
- function addSpeech(text: JString; packagename: JString; resourceId: Integer): Integer; cdecl; overload;
- function addSpeech(text: JString; filename: JString): Integer; cdecl; overload;
- function areDefaultsEnforced: Boolean; cdecl;
- function getDefaultEngine: JString; cdecl;
- function getDefaultLanguage: JLocale; cdecl;
- function getEngines: JList; cdecl;
- function getFeatures(locale: JLocale): JSet; cdecl;
- function getLanguage: JLocale; cdecl;
- function isLanguageAvailable(loc: JLocale): Integer; cdecl;
- function isSpeaking: Boolean; cdecl;
- function playEarcon(earcon: JString; queueMode: Integer; params: JHashMap): Integer; cdecl;
- function playSilence(durationInMs: Int64; queueMode: Integer; params: JHashMap): Integer; cdecl;
- function setEngineByPackageName(enginePackageName: JString): Integer; cdecl;//Deprecated
- function setLanguage(loc: JLocale): Integer; cdecl;
- function setOnUtteranceCompletedListener(listener: JTextToSpeech_OnUtteranceCompletedListener): Integer; cdecl;//Deprecated
- function setOnUtteranceProgressListener(listener: JUtteranceProgressListener): Integer; cdecl;
- function setPitch(pitch: Single): Integer; cdecl;
- function setSpeechRate(speechRate: Single): Integer; cdecl;
- procedure shutdown; cdecl;
- function speak(text: JString; queueMode: Integer; params: JHashMap): Integer; cdecl;
- function stop: Integer; cdecl;
- function synthesizeToFile(text: JString; params: JHashMap; filename: JString): Integer; cdecl;
- end;
- TJTextToSpeech = class(TJavaGenericImport) end;
-
-type
- JTextToSpeech_Engine = interface;
-
- JTextToSpeech_EngineClass = interface(JObjectClass)
- ['{75457E65-C0B1-4AF3-A166-A553887479C5}']
- {class} function _GetACTION_CHECK_TTS_DATA: JString;
- {class} function _GetACTION_GET_SAMPLE_TEXT: JString;
- {class} function _GetACTION_INSTALL_TTS_DATA: JString;
- {class} function _GetACTION_TTS_DATA_INSTALLED: JString;
- {class} function _GetCHECK_VOICE_DATA_BAD_DATA: Integer;
- {class} function _GetCHECK_VOICE_DATA_FAIL: Integer;
- {class} function _GetCHECK_VOICE_DATA_MISSING_DATA: Integer;
- {class} function _GetCHECK_VOICE_DATA_MISSING_VOLUME: Integer;
- {class} function _GetCHECK_VOICE_DATA_PASS: Integer;
- {class} function _GetDEFAULT_STREAM: Integer;
- {class} function _GetEXTRA_AVAILABLE_VOICES: JString;
- {class} function _GetEXTRA_CHECK_VOICE_DATA_FOR: JString;
- {class} function _GetEXTRA_SAMPLE_TEXT: JString;
- {class} function _GetEXTRA_TTS_DATA_INSTALLED: JString;
- {class} function _GetEXTRA_UNAVAILABLE_VOICES: JString;
- {class} function _GetEXTRA_VOICE_DATA_FILES: JString;
- {class} function _GetEXTRA_VOICE_DATA_FILES_INFO: JString;
- {class} function _GetEXTRA_VOICE_DATA_ROOT_DIRECTORY: JString;
- {class} function _GetINTENT_ACTION_TTS_SERVICE: JString;
- {class} function _GetKEY_FEATURE_EMBEDDED_SYNTHESIS: JString;
- {class} function _GetKEY_FEATURE_NETWORK_SYNTHESIS: JString;
- {class} function _GetKEY_PARAM_PAN: JString;
- {class} function _GetKEY_PARAM_STREAM: JString;
- {class} function _GetKEY_PARAM_UTTERANCE_ID: JString;
- {class} function _GetKEY_PARAM_VOLUME: JString;
- {class} function _GetSERVICE_META_DATA: JString;
- {class} function init: JTextToSpeech_Engine; cdecl;
- {class} property ACTION_CHECK_TTS_DATA: JString read _GetACTION_CHECK_TTS_DATA;
- {class} property ACTION_GET_SAMPLE_TEXT: JString read _GetACTION_GET_SAMPLE_TEXT;
- {class} property ACTION_INSTALL_TTS_DATA: JString read _GetACTION_INSTALL_TTS_DATA;
- {class} property ACTION_TTS_DATA_INSTALLED: JString read _GetACTION_TTS_DATA_INSTALLED;
- {class} property CHECK_VOICE_DATA_BAD_DATA: Integer read _GetCHECK_VOICE_DATA_BAD_DATA;
- {class} property CHECK_VOICE_DATA_FAIL: Integer read _GetCHECK_VOICE_DATA_FAIL;
- {class} property CHECK_VOICE_DATA_MISSING_DATA: Integer read _GetCHECK_VOICE_DATA_MISSING_DATA;
- {class} property CHECK_VOICE_DATA_MISSING_VOLUME: Integer read _GetCHECK_VOICE_DATA_MISSING_VOLUME;
- {class} property CHECK_VOICE_DATA_PASS: Integer read _GetCHECK_VOICE_DATA_PASS;
- {class} property DEFAULT_STREAM: Integer read _GetDEFAULT_STREAM;
- {class} property EXTRA_AVAILABLE_VOICES: JString read _GetEXTRA_AVAILABLE_VOICES;
- {class} property EXTRA_CHECK_VOICE_DATA_FOR: JString read _GetEXTRA_CHECK_VOICE_DATA_FOR;
- {class} property EXTRA_SAMPLE_TEXT: JString read _GetEXTRA_SAMPLE_TEXT;
- {class} property EXTRA_TTS_DATA_INSTALLED: JString read _GetEXTRA_TTS_DATA_INSTALLED;
- {class} property EXTRA_UNAVAILABLE_VOICES: JString read _GetEXTRA_UNAVAILABLE_VOICES;
- {class} property EXTRA_VOICE_DATA_FILES: JString read _GetEXTRA_VOICE_DATA_FILES;
- {class} property EXTRA_VOICE_DATA_FILES_INFO: JString read _GetEXTRA_VOICE_DATA_FILES_INFO;
- {class} property EXTRA_VOICE_DATA_ROOT_DIRECTORY: JString read _GetEXTRA_VOICE_DATA_ROOT_DIRECTORY;
- {class} property INTENT_ACTION_TTS_SERVICE: JString read _GetINTENT_ACTION_TTS_SERVICE;
- {class} property KEY_FEATURE_EMBEDDED_SYNTHESIS: JString read _GetKEY_FEATURE_EMBEDDED_SYNTHESIS;
- {class} property KEY_FEATURE_NETWORK_SYNTHESIS: JString read _GetKEY_FEATURE_NETWORK_SYNTHESIS;
- {class} property KEY_PARAM_PAN: JString read _GetKEY_PARAM_PAN;
- {class} property KEY_PARAM_STREAM: JString read _GetKEY_PARAM_STREAM;
- {class} property KEY_PARAM_UTTERANCE_ID: JString read _GetKEY_PARAM_UTTERANCE_ID;
- {class} property KEY_PARAM_VOLUME: JString read _GetKEY_PARAM_VOLUME;
- {class} property SERVICE_META_DATA: JString read _GetSERVICE_META_DATA;
- end;
-
- [JavaSignature('android/speech/tts/TextToSpeech$Engine')]
- JTextToSpeech_Engine = interface(JObject)
- ['{A876F830-EEA2-4A8E-B40D-B7AA567205EE}']
- end;
- TJTextToSpeech_Engine = class(TJavaGenericImport) end;
-{$ENDIF}
-
-type
- { IgoSpeechToText implementation }
- TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
- {$REGION 'Internal Declarations'}
- private type
- TInitListener = class(TJavaLocal, JTextToSpeech_OnInitListener)
- private
- [weak] FImplementation: TgoTextToSpeechImplementation;
- public
- { JTextToSpeech_OnInitListener }
- procedure onInit(status: Integer); cdecl;
- public
- constructor Create(const AImplementation: TgoTextToSpeechImplementation);
- end;
- private type
- TCompletedListener = class(TJavaLocal, JTextToSpeech_OnUtteranceCompletedListener)
- private
- [weak] FImplementation: TgoTextToSpeechImplementation;
- public
- { JTextToSpeech_OnUtteranceCompletedListener }
- procedure onUtteranceCompleted(utteranceId: JString); cdecl;
- public
- constructor Create(const AImplementation: TgoTextToSpeechImplementation);
- end;
- private
- FTextToSpeech: JTextToSpeech;
- FInitListener: TInitListener;
- FCompletedListener: TCompletedListener;
- FParams: JHashMap;
- FSpeechStarted: Boolean;
- private
- procedure Initialize(const AStatus: Integer);
- protected
- { IgoTextToSpeech }
- function Speak(const AText: String): Boolean; override;
- procedure Stop; override;
- function IsSpeaking: Boolean; override;
- {$ENDREGION 'Internal Declarations'}
- public
- constructor Create;
- end;
-
-implementation
-
-uses
- System.SysUtils,
- Androidapi.Helpers;
-
-{ TgoTextToSpeechImplementation }
-
-constructor TgoTextToSpeechImplementation.Create;
-begin
- inherited;
- FInitListener := TInitListener.Create(Self);
- FTextToSpeech := TJTextToSpeech.JavaClass.init(TAndroidHelper.Context, FInitListener);
-end;
-
-procedure TgoTextToSpeechImplementation.Initialize(const AStatus: Integer);
-begin
- FInitListener := nil;
- if (AStatus = TJTextToSpeech.JavaClass.SUCCESS) then
- begin
- Available := True;
- DoAvailable;
-
- FTextToSpeech.setLanguage(TJLocale.JavaClass.getDefault);
-
- { We need a hash map with a KEY_PARAM_UTTERANCE_ID parameter.
- Otherwise, onUtteranceCompleted will not get called. }
- FParams := TJHashMap.Create;
- FParams.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_UTTERANCE_ID, StringToJString('DummyUtteranceId'));
- FCompletedListener := TCompletedListener.Create(Self);
- FTextToSpeech.setOnUtteranceCompletedListener(FCompletedListener);
- end
- else
- FTextToSpeech := nil;
-end;
-
-function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
-begin
- Result := FSpeechStarted;
-end;
-
-function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
-begin
- if (AText.Trim = '') then
- Exit(True);
-
- if Assigned(FTextToSpeech) then
- begin
- Result := (FTextToSpeech.speak(StringToJString(AText),
- TJTextToSpeech.JavaClass.QUEUE_FLUSH, FParams) = TJTextToSpeech.JavaClass.SUCCESS);
- if (Result) then
- begin
- FSpeechStarted := True;
- DoSpeechStarted;
- end;
- end
- else
- Result := False;
-end;
-
-procedure TgoTextToSpeechImplementation.Stop;
-begin
- if Assigned(FTextToSpeech) then
- FTextToSpeech.stop;
-end;
-
-{ TgoTextToSpeechImplementation.TInitListener }
-
-constructor TgoTextToSpeechImplementation.TInitListener.Create(
- const AImplementation: TgoTextToSpeechImplementation);
-begin
- Assert(Assigned(AImplementation));
- inherited Create;
- FImplementation := AImplementation;
-end;
-
-procedure TgoTextToSpeechImplementation.TInitListener.onInit(status: Integer);
-begin
- if Assigned(FImplementation) then
- FImplementation.Initialize(status);
-end;
-
-{ TgoTextToSpeechImplementation.TCompletedListener }
-
-constructor TgoTextToSpeechImplementation.TCompletedListener.Create(
- const AImplementation: TgoTextToSpeechImplementation);
-begin
- Assert(Assigned(AImplementation));
- inherited Create;
- FImplementation := AImplementation;
-end;
-
-procedure TgoTextToSpeechImplementation.TCompletedListener.onUtteranceCompleted(
- utteranceId: JString);
-begin
- if Assigned(FImplementation) then
- begin
- FImplementation.FSpeechStarted := False;
- FImplementation.DoSpeechFinished;
- end;
-end;
-
-end.
+unit Grijjy.TextToSpeech.Android;
+{< Text To Speech engine implementation for Android }
+
+// Om: prefix = changes by oMAR mar20
+
+interface
+
+uses
+ System.Classes, //Om: for TStrings
+ FMX.Platform, //Om: plat services
+
+ Androidapi.JNIBridge,
+ Androidapi.JNI.JavaTypes,
+
+
+ {$IF RTLVersion >= 31}
+ Androidapi.JNI.Speech,
+ {$ELSE}
+ Androidapi.JNI.GraphicsContentViewText,
+ {$ENDIF}
+ Grijjy.TextToSpeech,
+ Grijjy.TextToSpeech.Base;
+
+{$IF RTLVersion < 31}
+{ Delphi 10 Seattle and ealier versions don't have imports for the Java
+ Text-to-Speech classes. So we imported the parts we need ourselves using
+ Java2OP. }
+
+type
+ JTextToSpeech_OnInitListenerClass = interface(IJavaClass)
+ ['{B01450B5-524A-4B99-95DC-9158B7B8CC15}']
+ end;
+
+ [JavaSignature('android/speech/tts/TextToSpeech$OnInitListener')]
+ JTextToSpeech_OnInitListener = interface(IJavaInstance)
+ ['{94CC537C-E958-4EA5-B613-1465AEF6014B}']
+ procedure onInit(status: Integer); cdecl;
+ end;
+ TJTextToSpeech_OnInitListener = class(TJavaGenericImport) end;
+
+type
+ JTextToSpeech_OnUtteranceCompletedListenerClass = interface(IJavaClass)
+ ['{83D093B7-6FB6-46FE-A08E-1B0D25BDA841}']
+ end;
+
+ [JavaSignature('android/speech/tts/TextToSpeech$OnUtteranceCompletedListener')]
+ JTextToSpeech_OnUtteranceCompletedListener = interface(IJavaInstance)
+ ['{3EA0D21E-74E4-4204-A18F-2F68FE126E18}']
+ procedure onUtteranceCompleted(utteranceId: JString); cdecl;
+ end;
+ TJTextToSpeech_OnUtteranceCompletedListener = class(TJavaGenericImport) end;
+
+type
+ JUtteranceProgressListener = interface;
+
+ JUtteranceProgressListenerClass = interface(JObjectClass)
+ ['{9D335A6E-78BE-4060-B3C1-6028E603073D}']
+ {class} function init: JUtteranceProgressListener; cdecl;
+ end;
+
+ [JavaSignature('android/speech/tts/UtteranceProgressListener')]
+ JUtteranceProgressListener = interface(JObject)
+ ['{75D1A7E1-86E7-47D6-B9EC-96F0D69DC535}']
+ procedure onDone(utteranceId: JString); cdecl;
+ procedure onError(utteranceId: JString); cdecl;
+ procedure onStart(utteranceId: JString); cdecl;
+ end;
+ TJUtteranceProgressListener = class(TJavaGenericImport) end;
+
+type
+ JTextToSpeech = interface;
+
+ JTextToSpeechClass = interface(JObjectClass)
+ ['{BE260883-0916-456E-B84C-6B237C8382DA}']
+ {class} function _GetACTION_TTS_QUEUE_PROCESSING_COMPLETED: JString;
+ {class} function _GetERROR: Integer;
+ {class} function _GetLANG_AVAILABLE: Integer;
+ {class} function _GetLANG_COUNTRY_AVAILABLE: Integer;
+ {class} function _GetLANG_COUNTRY_VAR_AVAILABLE: Integer;
+ {class} function _GetLANG_MISSING_DATA: Integer;
+ {class} function _GetLANG_NOT_SUPPORTED: Integer;
+ {class} function _GetQUEUE_ADD: Integer;
+ {class} function _GetQUEUE_FLUSH: Integer;
+ {class} function _GetSUCCESS: Integer;
+ {class} function init(context: JContext; listener: JTextToSpeech_OnInitListener): JTextToSpeech; cdecl; overload;
+ {class} function init(context: JContext; listener: JTextToSpeech_OnInitListener; engine: JString): JTextToSpeech; cdecl; overload;
+// {class} function getMaxSpeechInputLength: Integer; cdecl; { Requires Android 4.3 }
+ {class} property ACTION_TTS_QUEUE_PROCESSING_COMPLETED: JString read _GetACTION_TTS_QUEUE_PROCESSING_COMPLETED;
+ {class} property ERROR: Integer read _GetERROR;
+ {class} property LANG_AVAILABLE: Integer read _GetLANG_AVAILABLE;
+ {class} property LANG_COUNTRY_AVAILABLE: Integer read _GetLANG_COUNTRY_AVAILABLE;
+ {class} property LANG_COUNTRY_VAR_AVAILABLE: Integer read _GetLANG_COUNTRY_VAR_AVAILABLE;
+ {class} property LANG_MISSING_DATA: Integer read _GetLANG_MISSING_DATA;
+ {class} property LANG_NOT_SUPPORTED: Integer read _GetLANG_NOT_SUPPORTED;
+ {class} property QUEUE_ADD: Integer read _GetQUEUE_ADD;
+ {class} property QUEUE_FLUSH: Integer read _GetQUEUE_FLUSH;
+ {class} property SUCCESS: Integer read _GetSUCCESS;
+ end;
+
+ [JavaSignature('android/speech/tts/TextToSpeech')]
+ JTextToSpeech = interface(JObject)
+ ['{38B05C3C-B672-4FEC-849B-0CF4D89AA507}']
+ function addEarcon(earcon: JString; packagename: JString; resourceId: Integer): Integer; cdecl; overload;
+ function addEarcon(earcon: JString; filename: JString): Integer; cdecl; overload;
+ function addSpeech(text: JString; packagename: JString; resourceId: Integer): Integer; cdecl; overload;
+ function addSpeech(text: JString; filename: JString): Integer; cdecl; overload;
+ function areDefaultsEnforced: Boolean; cdecl;
+ function getDefaultEngine: JString; cdecl;
+ function getDefaultLanguage: JLocale; cdecl;
+ function getEngines: JList; cdecl;
+ function getFeatures(locale: JLocale): JSet; cdecl;
+ function getLanguage: JLocale; cdecl;
+ function isLanguageAvailable(loc: JLocale): Integer; cdecl;
+ function isSpeaking: Boolean; cdecl;
+ function playEarcon(earcon: JString; queueMode: Integer; params: JHashMap): Integer; cdecl;
+ function playSilence(durationInMs: Int64; queueMode: Integer; params: JHashMap): Integer; cdecl;
+ function setEngineByPackageName(enginePackageName: JString): Integer; cdecl;//Deprecated
+ function setLanguage(loc: JLocale): Integer; cdecl;
+ function setOnUtteranceCompletedListener(listener: JTextToSpeech_OnUtteranceCompletedListener): Integer; cdecl;//Deprecated
+ function setOnUtteranceProgressListener(listener: JUtteranceProgressListener): Integer; cdecl;
+ function setPitch(pitch: Single): Integer; cdecl;
+ function setSpeechRate(speechRate: Single): Integer; cdecl;
+ procedure shutdown; cdecl;
+ function speak(text: JString; queueMode: Integer; params: JHashMap): Integer; cdecl;
+ function stop: Integer; cdecl;
+ function synthesizeToFile(text: JString; params: JHashMap; filename: JString): Integer; cdecl;
+ end;
+ TJTextToSpeech = class(TJavaGenericImport) end;
+
+type
+ JTextToSpeech_Engine = interface;
+
+ JTextToSpeech_EngineClass = interface(JObjectClass)
+ ['{75457E65-C0B1-4AF3-A166-A553887479C5}']
+ {class} function _GetACTION_CHECK_TTS_DATA: JString;
+ {class} function _GetACTION_GET_SAMPLE_TEXT: JString;
+ {class} function _GetACTION_INSTALL_TTS_DATA: JString;
+ {class} function _GetACTION_TTS_DATA_INSTALLED: JString;
+ {class} function _GetCHECK_VOICE_DATA_BAD_DATA: Integer;
+ {class} function _GetCHECK_VOICE_DATA_FAIL: Integer;
+ {class} function _GetCHECK_VOICE_DATA_MISSING_DATA: Integer;
+ {class} function _GetCHECK_VOICE_DATA_MISSING_VOLUME: Integer;
+ {class} function _GetCHECK_VOICE_DATA_PASS: Integer;
+ {class} function _GetDEFAULT_STREAM: Integer;
+ {class} function _GetEXTRA_AVAILABLE_VOICES: JString;
+ {class} function _GetEXTRA_CHECK_VOICE_DATA_FOR: JString;
+ {class} function _GetEXTRA_SAMPLE_TEXT: JString;
+ {class} function _GetEXTRA_TTS_DATA_INSTALLED: JString;
+ {class} function _GetEXTRA_UNAVAILABLE_VOICES: JString;
+ {class} function _GetEXTRA_VOICE_DATA_FILES: JString;
+ {class} function _GetEXTRA_VOICE_DATA_FILES_INFO: JString;
+ {class} function _GetEXTRA_VOICE_DATA_ROOT_DIRECTORY: JString;
+ {class} function _GetINTENT_ACTION_TTS_SERVICE: JString;
+ {class} function _GetKEY_FEATURE_EMBEDDED_SYNTHESIS: JString;
+ {class} function _GetKEY_FEATURE_NETWORK_SYNTHESIS: JString;
+ {class} function _GetKEY_PARAM_PAN: JString;
+ {class} function _GetKEY_PARAM_STREAM: JString;
+ {class} function _GetKEY_PARAM_UTTERANCE_ID: JString;
+ {class} function _GetKEY_PARAM_VOLUME: JString;
+ {class} function _GetSERVICE_META_DATA: JString;
+ {class} function init: JTextToSpeech_Engine; cdecl;
+ {class} property ACTION_CHECK_TTS_DATA: JString read _GetACTION_CHECK_TTS_DATA;
+ {class} property ACTION_GET_SAMPLE_TEXT: JString read _GetACTION_GET_SAMPLE_TEXT;
+ {class} property ACTION_INSTALL_TTS_DATA: JString read _GetACTION_INSTALL_TTS_DATA;
+ {class} property ACTION_TTS_DATA_INSTALLED: JString read _GetACTION_TTS_DATA_INSTALLED;
+ {class} property CHECK_VOICE_DATA_BAD_DATA: Integer read _GetCHECK_VOICE_DATA_BAD_DATA;
+ {class} property CHECK_VOICE_DATA_FAIL: Integer read _GetCHECK_VOICE_DATA_FAIL;
+ {class} property CHECK_VOICE_DATA_MISSING_DATA: Integer read _GetCHECK_VOICE_DATA_MISSING_DATA;
+ {class} property CHECK_VOICE_DATA_MISSING_VOLUME: Integer read _GetCHECK_VOICE_DATA_MISSING_VOLUME;
+ {class} property CHECK_VOICE_DATA_PASS: Integer read _GetCHECK_VOICE_DATA_PASS;
+ {class} property DEFAULT_STREAM: Integer read _GetDEFAULT_STREAM;
+ {class} property EXTRA_AVAILABLE_VOICES: JString read _GetEXTRA_AVAILABLE_VOICES;
+ {class} property EXTRA_CHECK_VOICE_DATA_FOR: JString read _GetEXTRA_CHECK_VOICE_DATA_FOR;
+ {class} property EXTRA_SAMPLE_TEXT: JString read _GetEXTRA_SAMPLE_TEXT;
+ {class} property EXTRA_TTS_DATA_INSTALLED: JString read _GetEXTRA_TTS_DATA_INSTALLED;
+ {class} property EXTRA_UNAVAILABLE_VOICES: JString read _GetEXTRA_UNAVAILABLE_VOICES;
+ {class} property EXTRA_VOICE_DATA_FILES: JString read _GetEXTRA_VOICE_DATA_FILES;
+ {class} property EXTRA_VOICE_DATA_FILES_INFO: JString read _GetEXTRA_VOICE_DATA_FILES_INFO;
+ {class} property EXTRA_VOICE_DATA_ROOT_DIRECTORY: JString read _GetEXTRA_VOICE_DATA_ROOT_DIRECTORY;
+ {class} property INTENT_ACTION_TTS_SERVICE: JString read _GetINTENT_ACTION_TTS_SERVICE;
+ {class} property KEY_FEATURE_EMBEDDED_SYNTHESIS: JString read _GetKEY_FEATURE_EMBEDDED_SYNTHESIS;
+ {class} property KEY_FEATURE_NETWORK_SYNTHESIS: JString read _GetKEY_FEATURE_NETWORK_SYNTHESIS;
+ {class} property KEY_PARAM_PAN: JString read _GetKEY_PARAM_PAN;
+ {class} property KEY_PARAM_STREAM: JString read _GetKEY_PARAM_STREAM;
+ {class} property KEY_PARAM_UTTERANCE_ID: JString read _GetKEY_PARAM_UTTERANCE_ID;
+ {class} property KEY_PARAM_VOLUME: JString read _GetKEY_PARAM_VOLUME;
+ {class} property SERVICE_META_DATA: JString read _GetSERVICE_META_DATA;
+ end;
+
+ [JavaSignature('android/speech/tts/TextToSpeech$Engine')]
+ JTextToSpeech_Engine = interface(JObject)
+ ['{A876F830-EEA2-4A8E-B40D-B7AA567205EE}']
+ end;
+ TJTextToSpeech_Engine = class(TJavaGenericImport) end;
+{$ENDIF}
+
+type
+ { IgoSpeechToText implementation }
+ TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
+ {$REGION 'Internal Declarations'}
+ private type
+ TInitListener = class(TJavaLocal, JTextToSpeech_OnInitListener)
+ private
+ [weak] FImplementation: TgoTextToSpeechImplementation;
+ public
+ { JTextToSpeech_OnInitListener }
+ procedure onInit(status: Integer); cdecl;
+ public
+ constructor Create(const AImplementation: TgoTextToSpeechImplementation);
+ end;
+ private type
+ TCompletedListener = class(TJavaLocal, JTextToSpeech_OnUtteranceCompletedListener)
+ private
+ [weak] FImplementation: TgoTextToSpeechImplementation;
+ public
+ { JTextToSpeech_OnUtteranceCompletedListener }
+ procedure onUtteranceCompleted(utteranceId: JString); cdecl;
+ public
+ constructor Create(const AImplementation: TgoTextToSpeechImplementation);
+ end;
+ private
+ FTextToSpeech: JTextToSpeech;
+ FInitListener: TInitListener;
+ FCompletedListener: TCompletedListener;
+ FParams: JHashMap;
+ FSpeechStarted: Boolean;
+
+ // objects below are set if found
+
+ fNativeVoice :JVoice; // current language (used in last line)
+ fMaleVoice :JVoice; // male and female voices
+ fFemaleVoice :JVoice;
+
+ private
+ procedure Initialize(const AStatus: Integer);
+ procedure getNativeVoices;
+ protected
+ { IgoTextToSpeech }
+ function getVoices(aList:TStrings):boolean; override; // Om: mar20: get list of available voices ( only for iOS at this time)
+ function getVoiceGender:TVoiceGender; override; // Om: mar20:
+ function setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; override; // Om: mar20: set voice w/ spec like 'pt-br' (lang-country)
+
+ function Speak(const AText: String): Boolean; override;
+ procedure Stop; override;
+ function IsSpeaking: Boolean; override;
+ {$ENDREGION 'Internal Declarations'}
+ public
+ constructor Create;
+ end;
+
+// function getDeviceCountryCode:String; //platform specific get country code
+
+
+implementation //-----------------------------------
+
+uses
+ System.SysUtils,
+ Androidapi.Helpers;
+
+function getDeviceCountryCode:String; //platform specific get country code
+var Locale: JLocale;
+begin
+ Result:='??';
+
+ Locale := TJLocale.JavaClass.getDefault;
+ Result := JStringToString( Locale.getCountry );
+
+ if Length(Result) > 2 then Delete(Result, 3, MaxInt);
+end;
+
+function getOSLanguage:String;
+var LocServ: IFMXLocaleService;
+begin
+ if TPlatformServices.Current.SupportsPlatformService(IFMXLocaleService, IInterface(LocServ)) then
+ Result := LocServ.GetCurrentLangID // 2 letter code, but a different one ???? instead of 'pt' it gives 'po'
+ else Result := '??';
+
+ // if set Japanese on Android, LocaleService returns "jp", but other platform returns "ja"
+ // so I think it is better to change "jp" to "ja"
+ if (Result = 'jp') then Result := 'ja'
+ else if (Result = 'po') then Result := 'pt';
+
+end;
+
+{ TgoTextToSpeechImplementation }
+
+constructor TgoTextToSpeechImplementation.Create;
+begin
+ inherited;
+ FInitListener := TInitListener.Create(Self);
+ FTextToSpeech := TJTextToSpeech.JavaClass.init(TAndroidHelper.Context, FInitListener);
+
+ fNativeVoice := nil; //not set yet
+ fMaleVoice := nil;
+ fFemaleVoice := nil;
+end;
+
+// Om: mar20:
+function TgoTextToSpeechImplementation.getVoices(aList: TStrings): boolean;
+var aVoicesLst:JSet;
+ it:Jiterator;
+ v :JVoice;
+ s :String;
+ n :integer;
+
+ vname,vlang,vcountry:String;
+
+begin
+ Result := false;
+ aVoicesLst := FTextToSpeech.getVoices;
+ it := aVoicesLst.iterator;
+ n :=0;
+ while it.hasNext do
+ begin
+ inc(n);
+ v := TJVoice.Wrap( it.next );
+
+ vname := jstringtostring( v.getName ); //
+ vlang := jstringtostring( v.getLocale.getLanguage ); // por
+ vcountry := jstringtostring( v.getLocale.getCountry ); // BRA
+
+ s := IntToStr(n) +' '+ // str descr of voice
+ vname +' '+ // tipo pt-BR-SMTm00
+ vlang +' '+ // por
+ vcountry; // BRA
+
+ aList.Add( s );
+
+ s := jstringtostring( v.toString ); // Voice[Name: en-US-SMTf00, locale:...
+ aList.Add( s );
+
+ Result := true;
+ end;
+end;
+
+function TgoTextToSpeechImplementation.getVoiceGender:TVoiceGender; // Om: mar20:
+begin
+ if (fNativeVoice=fFemaleVoice) then Result := vgFemale
+ else if (fNativeVoice=fMaleVoice) then Result := vgMale
+ else Result := vgUnkown;
+end;
+
+function TgoTextToSpeechImplementation.setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; // Om: mar20: set voice w/ spec like 'pt-BR'
+var aVoicesLst:JSet;
+ it:Jiterator;
+ v :JVoice;
+ vname,vlang,vcountry,aLangCode,Lang2:String;
+ Sex:Char;
+
+begin
+ fNativeVoice := nil;
+ fMaleVoice := nil;
+ fFemaleVoice := nil;
+
+ aVoicesLst := FTextToSpeech.getVoices;
+ it := aVoicesLst.iterator;
+
+ while it.hasNext do
+ begin
+ v := TJVoice.Wrap( it.next ); // 123456789.123
+ vname := jstringtostring( v.getName ); // 'es-MX-SMTf00'
+ aLangCode := Copy(vname,1,5); // 'es-MX'
+ Lang2 := Copy(vname,7,6); // 'SMTf00'
+ if (Pos('f',Lang2)>0) then Sex:='f' else Sex:='m'; //extract gender from Lang2
+
+ vlang := jstringtostring( v.getLocale.getLanguage ); // por
+ vcountry := jstringtostring( v.getLocale.getCountry ); // BRA
+
+ if (CompareText(aLangCode,aFemaleVoiceLang)=0) and (Sex='f') then //found language
+ fFemaleVoice := v;
+ if (CompareText(aLangCode,aMaleVoiceLang)=0) and (Sex='m') then //found language
+ fMaleVoice := v;
+
+ /// if ( CompareText(vlang,'por')=0 ) and ( CompareText(vcountry,'BRA')=0 ) then
+ // fMaleVoice := v; // CHECK: Can we save the inteface for latter use ?
+ // // Android não tem brazuka mulher. Usa a mexicana..
+ // if ( CompareText(vlang,'spa')=0 ) and ( CompareText(vcountry,'MEX')=0 ) then
+ // fFemaleVoice := v;
+ end;
+
+ if Assigned(fMaleVoice) then fNativeVoice := fMaleVoice; //any voice will do, but..
+ if Assigned(fFemaleVoice) then fNativeVoice := fFemaleVoice; //.. default = female
+end;
+
+procedure TgoTextToSpeechImplementation.Initialize(const AStatus: Integer);
+begin
+ FInitListener := nil;
+ if (AStatus = TJTextToSpeech.JavaClass.SUCCESS) then
+ begin
+ Available := True;
+ DoAvailable;
+
+ FTextToSpeech.setLanguage(TJLocale.JavaClass.getDefault);
+
+ { We need a hash map with a KEY_PARAM_UTTERANCE_ID parameter.
+ Otherwise, onUtteranceCompleted will not get called. }
+ FParams := TJHashMap.Create;
+ FParams.put(TJTextToSpeech_Engine.JavaClass.KEY_PARAM_UTTERANCE_ID, StringToJString('DummyUtteranceId'));
+ FCompletedListener := TCompletedListener.Create(Self);
+ FTextToSpeech.setOnUtteranceCompletedListener(FCompletedListener);
+
+ getNativeVoices; //try to locate two suitable voices ( one male one female )
+ end
+ else FTextToSpeech := nil;
+end;
+
+procedure TgoTextToSpeechImplementation.getNativeVoices; //Om:
+var aVoicesLst:JSet;
+ it:Jiterator;
+ v :JVoice;
+ vname,vlang,vcountry,aLangCode,Lang2:String;
+ Sex:Char;
+begin
+ fNativeVoice := nil;
+ fMaleVoice := nil;
+ fFemaleVoice := nil;
+
+ aVoicesLst := FTextToSpeech.getVoices;
+ it := aVoicesLst.iterator;
+
+ while it.hasNext do
+ begin
+ v := TJVoice.Wrap( it.next );
+
+ vname := jstringtostring( v.getName ); // es-MEX-SMTf00
+ //vlang := jstringtostring( v.getLocale.getLanguage ); // por
+ //vcountry := jstringtostring( v.getLocale.getCountry ); // BRA
+
+ aLangCode := Copy(vname,1,5); // 'es-MX'
+ Lang2 := Copy(vname,7,6); // 'SMTf00'
+
+ if (NativeSpeechLanguage<>'??-??') and ( CompareText(aLangCode,NativeSpeechLanguage)=0 ) then // NativeSpeechLanguage in Grijjy.TextToSpeech.pas
+ begin
+ if (Pos('f',Lang2)>0) then Sex:='f' else Sex:='m'; //extract gender from Lang2
+ if (Sex='m') then fMaleVoice := v //locate voices of guy and girl
+ else if (Sex='f') then fFemaleVoice := v;
+ end;
+
+ // oMAR: some old ad hoc tests
+ // if ( CompareText(vlang,'por')=0 ) and ( CompareText(vcountry,'BRA')=0 ) then
+ // fMaleVoice := v; // CHECK: Can we save the inteface for latter use ?
+ // // não tem brazuka mulher. Usa a mexicana..
+ // if ( CompareText(vlang,'spa')=0 ) and ( CompareText(vcountry,'MEX')=0 ) then
+ // fFemaleVoice := v;
+ end;
+
+ if Assigned(fMaleVoice) then fNativeVoice := fMaleVoice; //any voice will do, but..
+ if Assigned(fFemaleVoice) then fNativeVoice := fFemaleVoice; //.. default = female
+end;
+
+function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
+begin
+ Result := FSpeechStarted;
+end;
+
+function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
+begin
+ if (AText.Trim = '') then
+ Exit(True);
+
+ if Assigned(FTextToSpeech) then
+ begin
+
+ // Om: Use saved voice, if any
+ if Assigned(fFemaleVoice) and Assigned(fMaleVoice) then //alternating male-female voices
+ begin
+ if (fNativeVoice=fFemaleVoice) then fNativeVoice:=fMaleVoice
+ else fNativeVoice:=fFemaleVoice;
+ end;
+
+ if Assigned(fNativeVoice) then
+ FTextToSpeech.setVoice( fNativeVoice );
+
+ Result := (FTextToSpeech.speak(StringToJString(AText),
+ TJTextToSpeech.JavaClass.QUEUE_FLUSH, FParams) = TJTextToSpeech.JavaClass.SUCCESS);
+ if (Result) then
+ begin
+ FSpeechStarted := True;
+ DoSpeechStarted;
+ end;
+ end
+ else
+ Result := False;
+end;
+
+procedure TgoTextToSpeechImplementation.Stop;
+begin
+ if Assigned(FTextToSpeech) then
+ FTextToSpeech.stop;
+end;
+
+{ TgoTextToSpeechImplementation.TInitListener }
+
+constructor TgoTextToSpeechImplementation.TInitListener.Create(const AImplementation: TgoTextToSpeechImplementation);
+begin
+ Assert(Assigned(AImplementation));
+ inherited Create;
+ FImplementation := AImplementation;
+end;
+
+procedure TgoTextToSpeechImplementation.TInitListener.onInit(status: Integer);
+begin
+ if Assigned(FImplementation) then
+ FImplementation.Initialize(status);
+end;
+
+{ TgoTextToSpeechImplementation.TCompletedListener }
+
+constructor TgoTextToSpeechImplementation.TCompletedListener.Create(
+ const AImplementation: TgoTextToSpeechImplementation);
+begin
+ Assert(Assigned(AImplementation));
+ inherited Create;
+ FImplementation := AImplementation;
+end;
+
+procedure TgoTextToSpeechImplementation.TCompletedListener.onUtteranceCompleted( utteranceId: JString);
+begin
+ if Assigned(FImplementation) then
+ begin
+ FImplementation.FSpeechStarted := False;
+ FImplementation.DoSpeechFinished;
+ end;
+end;
+
+
+procedure getNativeVoiceLanguage;
+begin
+ NativeSpeechLanguage := getOSLanguage+'-'+getDeviceCountryCode; // 'pt-BR'
+end;
+
+initialization
+ getNativeVoiceLanguage; // get OS language settings
+end.
diff --git a/TextToSpeech/Grijjy.TextToSpeech.Base.pas b/TextToSpeech/Grijjy.TextToSpeech.Base.pas
index 7c1b99e..55b6c57 100644
--- a/TextToSpeech/Grijjy.TextToSpeech.Base.pas
+++ b/TextToSpeech/Grijjy.TextToSpeech.Base.pas
@@ -1,122 +1,126 @@
-unit Grijjy.TextToSpeech.Base;
-{< Base class for text-to-speech implementations }
-
-interface
-
-uses
- System.Classes,
- Grijjy.TextToSpeech;
-
-type
- { Base class that implements IgoTextToSpeech.
- Platform-specific implementations derive from this class. }
- TgoTextToSpeechBase = class abstract(TInterfacedObject, IgoTextToSpeech)
- {$REGION 'Internal Declarations'}
- private
- FAvailable: Boolean;
- FOnAvailable: TNotifyEvent;
- FOnSpeechStarted: TNotifyEvent;
- FOnSpeechFinished: TNotifyEvent;
- protected
- { IgoTextToSpeech }
- function _GetAvailable: Boolean;
- function _GetOnAvailable: TNotifyEvent;
- procedure _SetOnAvailable(const AValue: TNotifyEvent);
- function _GetOnSpeechFinished: TNotifyEvent;
- procedure _SetOnSpeechFinished(const AValue: TNotifyEvent);
- function _GetOnSpeechStarted: TNotifyEvent;
- procedure _SetOnSpeechStarted(const AValue: TNotifyEvent);
-
- function Speak(const AText: String): Boolean; virtual; abstract;
- procedure Stop; virtual; abstract;
- function IsSpeaking: Boolean; virtual; abstract;
- protected
- { Fires the FOn* events in the main thread }
- procedure DoAvailable;
- procedure DoSpeechStarted;
- procedure DoSpeechFinished;
-
- property Available: Boolean read FAvailable write FAvailable;
- {$ENDREGION 'Internal Declarations'}
- end;
-
-implementation
-
-{ TgoTextToSpeechBase }
-
-procedure TgoTextToSpeechBase.DoAvailable;
-begin
- if Assigned(FOnAvailable) then
- begin
- { Fire event from main thread. Use Queue instead of Synchronize to avoid
- blocking. }
- TThread.Queue(nil,
- procedure
- begin
- FOnAvailable(Self);
- end);
- end;
-end;
-
-procedure TgoTextToSpeechBase.DoSpeechFinished;
-begin
- if Assigned(FOnSpeechFinished) then
- begin
- TThread.Queue(nil,
- procedure
- begin
- FOnSpeechFinished(Self);
- end);
- end;
-end;
-
-procedure TgoTextToSpeechBase.DoSpeechStarted;
-begin
- if Assigned(FOnSpeechStarted) then
- begin
- TThread.Queue(nil,
- procedure
- begin
- FOnSpeechStarted(Self);
- end);
- end;
-end;
-
-function TgoTextToSpeechBase._GetAvailable: Boolean;
-begin
- Result := FAvailable;
-end;
-
-function TgoTextToSpeechBase._GetOnAvailable: TNotifyEvent;
-begin
- Result := FOnAvailable;
-end;
-
-function TgoTextToSpeechBase._GetOnSpeechFinished: TNotifyEvent;
-begin
- Result := FOnSpeechFinished;
-end;
-
-function TgoTextToSpeechBase._GetOnSpeechStarted: TNotifyEvent;
-begin
- Result := FOnSpeechStarted;
-end;
-
-procedure TgoTextToSpeechBase._SetOnAvailable(const AValue: TNotifyEvent);
-begin
- FOnAvailable := AValue;
- if (FAvailable) then
- DoAvailable;
-end;
-
-procedure TgoTextToSpeechBase._SetOnSpeechFinished(const AValue: TNotifyEvent);
-begin
- FOnSpeechFinished := AValue;
-end;
-
-procedure TgoTextToSpeechBase._SetOnSpeechStarted(const AValue: TNotifyEvent);
-begin
- FOnSpeechStarted := AValue;
-end;
-
-end.
+unit Grijjy.TextToSpeech.Base;
+{< Base class for text-to-speech implementations }
+
+interface
+
+uses
+ System.Classes,
+ Grijjy.TextToSpeech;
+
+type
+ { Base class that implements IgoTextToSpeech.
+ Platform-specific implementations derive from this class. }
+ TgoTextToSpeechBase = class abstract(TInterfacedObject, IgoTextToSpeech)
+ {$REGION 'Internal Declarations'}
+ private
+ FAvailable: Boolean;
+ FOnAvailable: TNotifyEvent;
+ FOnSpeechStarted: TNotifyEvent;
+ FOnSpeechFinished: TNotifyEvent;
+ protected
+ { IgoTextToSpeech }
+ function _GetAvailable: Boolean;
+ function _GetOnAvailable: TNotifyEvent;
+ procedure _SetOnAvailable(const AValue: TNotifyEvent);
+ function _GetOnSpeechFinished: TNotifyEvent;
+ procedure _SetOnSpeechFinished(const AValue: TNotifyEvent);
+ function _GetOnSpeechStarted: TNotifyEvent;
+ procedure _SetOnSpeechStarted(const AValue: TNotifyEvent);
+
+ function getVoices(aList:TStrings):boolean; virtual; abstract; // Om: mar20: get list of available voices ( only for iOS at this time)
+ function getVoiceGender:TVoiceGender; virtual; abstract; // Om: mar20:
+ function setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; virtual; abstract; // Om: mar20: set voice w/ spec like 'pt' (lang-country)
+
+ function Speak(const AText: String): Boolean; virtual; abstract;
+ procedure Stop; virtual; abstract;
+ function IsSpeaking: Boolean; virtual; abstract;
+ protected
+ { Fires the FOn* events in the main thread }
+ procedure DoAvailable;
+ procedure DoSpeechStarted;
+ procedure DoSpeechFinished;
+
+ property Available: Boolean read FAvailable write FAvailable;
+ {$ENDREGION 'Internal Declarations'}
+ end;
+
+implementation
+
+{ TgoTextToSpeechBase }
+
+procedure TgoTextToSpeechBase.DoAvailable;
+begin
+ if Assigned(FOnAvailable) then
+ begin
+ { Fire event from main thread. Use Queue instead of Synchronize to avoid
+ blocking. }
+ TThread.Queue(nil,
+ procedure
+ begin
+ FOnAvailable(Self);
+ end);
+ end;
+end;
+
+procedure TgoTextToSpeechBase.DoSpeechFinished;
+begin
+ if Assigned(FOnSpeechFinished) then
+ begin
+ TThread.Queue(nil,
+ procedure
+ begin
+ FOnSpeechFinished(Self);
+ end);
+ end;
+end;
+
+procedure TgoTextToSpeechBase.DoSpeechStarted;
+begin
+ if Assigned(FOnSpeechStarted) then
+ begin
+ TThread.Queue(nil,
+ procedure
+ begin
+ FOnSpeechStarted(Self);
+ end);
+ end;
+end;
+
+function TgoTextToSpeechBase._GetAvailable: Boolean;
+begin
+ Result := FAvailable;
+end;
+
+function TgoTextToSpeechBase._GetOnAvailable: TNotifyEvent;
+begin
+ Result := FOnAvailable;
+end;
+
+function TgoTextToSpeechBase._GetOnSpeechFinished: TNotifyEvent;
+begin
+ Result := FOnSpeechFinished;
+end;
+
+function TgoTextToSpeechBase._GetOnSpeechStarted: TNotifyEvent;
+begin
+ Result := FOnSpeechStarted;
+end;
+
+procedure TgoTextToSpeechBase._SetOnAvailable(const AValue: TNotifyEvent);
+begin
+ FOnAvailable := AValue;
+ if (FAvailable) then
+ DoAvailable;
+end;
+
+procedure TgoTextToSpeechBase._SetOnSpeechFinished(const AValue: TNotifyEvent);
+begin
+ FOnSpeechFinished := AValue;
+end;
+
+procedure TgoTextToSpeechBase._SetOnSpeechStarted(const AValue: TNotifyEvent);
+begin
+ FOnSpeechStarted := AValue;
+end;
+
+end.
diff --git a/TextToSpeech/Grijjy.TextToSpeech.Windows.pas b/TextToSpeech/Grijjy.TextToSpeech.Windows.pas
index 09d271f..7dc8a31 100644
--- a/TextToSpeech/Grijjy.TextToSpeech.Windows.pas
+++ b/TextToSpeech/Grijjy.TextToSpeech.Windows.pas
@@ -1,356 +1,632 @@
-unit Grijjy.TextToSpeech.Windows;
-{< Text To Speech engine implementation for Windows }
-
-interface
-
-uses
- Winapi.Windows,
- Winapi.ActiveX,
- Grijjy.TextToSpeech.Base;
-
-{ Partial import of sapi.dll type library }
-
-const
- CLASS_SpVoice: TGUID = '{96749377-3391-11D2-9EE3-00C04F797396}';
-
-type
- SPVISEMES = TOleEnum;
- SPVPRIORITY = TOleEnum;
- SPEVENTENUM = TOleEnum;
-
-type
- {$ALIGN 8}
- PSPEVENT = ^SPEVENT;
- SPEVENT = record
- eEventId: Word;
- elParamType: Word;
- ulStreamNum: ULONG;
- ullAudioStreamOffset: ULONGLONG;
- wParam: WPARAM;
- lParam: LPARAM;
- end;
-
-type
- {$ALIGN 8}
- PSPEVENTSOURCEINFO = ^SPEVENTSOURCEINFO;
- SPEVENTSOURCEINFO = record
- ullEventInterest: ULONGLONG;
- ullQueuedInterest: ULONGLONG;
- ulCount: ULONG;
- end;
-
-type
- {$ALIGN 4}
- PSPVOICESTATUS = ^SPVOICESTATUS;
- SPVOICESTATUS = record
- ulCurrentStream: ULONG;
- ulLastStreamQueued: ULONG;
- hrLastResult: HResult;
- dwRunningState: LongWord;
- ulInputWordPos: ULONG;
- ulInputWordLen: ULONG;
- ulInputSentPos: ULONG;
- ulInputSentLen: ULONG;
- lBookmarkId: LONG;
- PhonemeId: WideChar;
- VisemeId: SPVISEMES;
- dwReserved1: LongWord;
- dwReserved2: LongWord;
- end;
-
-type
- SPNOTIFYCALLBACK = procedure (wParam: WPARAM; lParam: LPARAM); stdcall;
-
-type
- // *********************************************************************//
- // Interface: ISpNotifySink
- // Flags: (512) Restricted
- // GUID: {259684DC-37C3-11D2-9603-00C04F8EE628}
- // *********************************************************************//
- ISpNotifySink = interface(IUnknown)
- ['{259684DC-37C3-11D2-9603-00C04F8EE628}']
- function Notify: HResult; stdcall;
- end;
-
-type
- // *********************************************************************//
- // Interface: ISpNotifySource
- // Flags: (512) Restricted
- // GUID: {5EFF4AEF-8487-11D2-961C-00C04F8EE628}
- // *********************************************************************//
- ISpNotifySource = interface(IUnknown)
- ['{5EFF4AEF-8487-11D2-961C-00C04F8EE628}']
- function SetNotifySink(const pNotifySink: ISpNotifySink): HResult; stdcall;
- function SetNotifyWindowMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
- lParam: LPARAM): HResult; stdcall;
- function SetNotifyCallbackFunction(pfnCallback: SPNOTIFYCALLBACK;
- wParam: WPARAM; lParam: LPARAM): HResult; stdcall;
- function SetNotifyCallbackInterface(pSpCallback: Pointer;
- wParam: WPARAM; lParam: LPARAM): HResult; stdcall;
- function SetNotifyWin32Event: HResult; stdcall;
- function WaitForNotifyEvent(dwMilliseconds: LongWord): HResult; stdcall;
- function GetNotifyEventHandle: Pointer; stdcall;
- end;
-
-type
- // *********************************************************************//
- // Interface: ISpEventSource
- // Flags: (512) Restricted
- // GUID: {BE7A9CCE-5F9E-11D2-960F-00C04F8EE628}
- // *********************************************************************//
- ISpEventSource = interface(ISpNotifySource)
- ['{BE7A9CCE-5F9E-11D2-960F-00C04F8EE628}']
- function SetInterest(ullEventInterest: ULONGLONG; ullQueuedInterest: ULONGLONG): HResult; stdcall;
- function GetEvents(ulCount: ULONG; pEventArray: PSPEVENT; out pulFetched: ULONG): HResult; stdcall;
- function GetInfo(out pInfo: SPEVENTSOURCEINFO): HResult; stdcall;
- end;
-
-type
- // *********************************************************************//
- // Interface: ISpVoice
- // Flags: (512) Restricted
- // GUID: {6C44DF74-72B9-4992-A1EC-EF996E0422D4}
- // *********************************************************************//
- ISpVoice = interface(ISpEventSource)
- ['{6C44DF74-72B9-4992-A1EC-EF996E0422D4}']
- function SetOutput(const pUnkOutput: IUnknown;
- fAllowFormatChanges: BOOL): HResult; stdcall;
- function GetOutputObjectToken(out ppObjectToken: IUnknown): HResult; stdcall;
- function GetOutputStream(out ppStream: IUnknown): HResult; stdcall;
- function Pause: HResult; stdcall;
- function Resume: HResult; stdcall;
- function SetVoice(const pToken: IUnknown): HResult; stdcall;
- function GetVoice(out ppToken: IUnknown): HResult; stdcall;
- function Speak(pwcs: LPCWSTR; dwFlags: LongWord;
- pulStreamNumber: PULONG): HResult; stdcall;
- function SpeakStream(const pStream: IUnknown; dwFlags: LongWord;
- out pulStreamNumber: LongWord): HResult; stdcall;
- function GetStatus(out pStatus: SPVOICESTATUS;
- ppszLastBookmark: PPWideChar): HResult; stdcall;
- function Skip(pItemType: LPCWSTR; lNumItems: Integer;
- out pulNumSkipped: ULONG): HResult; stdcall;
- function SetPriority(ePriority: SPVPRIORITY): HResult; stdcall;
- function GetPriority(out pePriority: SPVPRIORITY): HResult; stdcall;
- function SetAlertBoundary(eBoundary: SPEVENTENUM): HResult; stdcall;
- function GetAlertBoundary(out peBoundary: SPEVENTENUM): HResult; stdcall;
- function SetRate(RateAdjust: Integer): HResult; stdcall;
- function GetRate(out pRateAdjust: Integer): HResult; stdcall;
- function SetVolume(usVolume: Word): HResult; stdcall;
- function GetVolume(out pusVolume: Word): HResult; stdcall;
- function WaitUntilDone(msTimeout: LongWord): HResult; stdcall;
- function SetSyncSpeakTimeout(msTimeout: LongWord): HResult; stdcall;
- function GetSyncSpeakTimeout(out pmsTimeout: LongWord): HResult; stdcall;
- function SpeakCompleteEvent: Pointer; stdcall;
- function IsUISupported(pszTypeOfUI: PWideChar; pvExtraData: Pointer;
- cbExtraData: LongWord; out pfSupported: Integer): HResult; stdcall;
- function DisplayUI(hWndParent: HWND; pszTitle: PWideChar;
- pszTypeOfUI: PWideChar; pvExtraData: Pointer;
- cbExtraData: LongWord): HResult; stdcall;
- end;
-
-const
- // SPEVENTENUM values
- SPEI_UNDEFINED = $00000000;
- SPEI_START_INPUT_STREAM = $00000001;
- SPEI_END_INPUT_STREAM = $00000002;
- SPEI_VOICE_CHANGE = $00000003;
- SPEI_TTS_BOOKMARK = $00000004;
- SPEI_WORD_BOUNDARY = $00000005;
- SPEI_PHONEME = $00000006;
- SPEI_SENTENCE_BOUNDARY = $00000007;
- SPEI_VISEME = $00000008;
- SPEI_TTS_AUDIO_LEVEL = $00000009;
- SPEI_TTS_PRIVATE = $0000000F;
- SPEI_MIN_TTS = $00000001;
- SPEI_MAX_TTS = $0000000F;
- SPEI_END_SR_STREAM = $00000022;
- SPEI_SOUND_START = $00000023;
- SPEI_SOUND_END = $00000024;
- SPEI_PHRASE_START = $00000025;
- SPEI_RECOGNITION = $00000026;
- SPEI_HYPOTHESIS = $00000027;
- SPEI_SR_BOOKMARK = $00000028;
- SPEI_PROPERTY_NUM_CHANGE = $00000029;
- SPEI_PROPERTY_STRING_CHANGE = $0000002A;
- SPEI_FALSE_RECOGNITION = $0000002B;
- SPEI_INTERFERENCE = $0000002C;
- SPEI_REQUEST_UI = $0000002D;
- SPEI_RECO_STATE_CHANGE = $0000002E;
- SPEI_ADAPTATION = $0000002F;
- SPEI_START_SR_STREAM = $00000030;
- SPEI_RECO_OTHER_CONTEXT = $00000031;
- SPEI_SR_AUDIO_LEVEL = $00000032;
- SPEI_SR_RETAINEDAUDIO = $00000033;
- SPEI_SR_PRIVATE = $00000034;
- SPEI_ACTIVE_CATEGORY_CHANGED = $00000035;
- SPEI_RESERVED5 = $00000036;
- SPEI_RESERVED6 = $00000037;
- SPEI_MIN_SR = $00000022;
- SPEI_MAX_SR = $00000037;
- SPEI_RESERVED1 = $0000001E;
- SPEI_RESERVED2 = $00000021;
- SPEI_RESERVED3 = $0000003F;
-
-const
- // SPRUNSTATE flags
- SPRS_DONE = 1 shl 0;
- SPRS_IS_SPEAKING = 1 shl 1;
-
-const
- // SPEAKFLAGS flags
- SPF_DEFAULT = 0;
- SPF_ASYNC = 1 shl 0;
- SPF_PURGEBEFORESPEAK = 1 shl 1;
- SPF_IS_FILENAME = 1 shl 2;
- SPF_IS_XML = 1 shl 3;
- SPF_IS_NOT_XML = 1 shl 4;
- SPF_PERSIST_XML = 1 shl 5;
- SPF_NLP_SPEAK_PUNC = 1 shl 6;
- SPF_PARSE_SAPI = 1 shl 7;
- SPF_PARSE_SSML = 1 shl 8;
- SPF_PARSE_AUTODETECT = 0;
- SPF_NLP_MASK = SPF_NLP_SPEAK_PUNC;
- SPF_PARSE_MASK = SPF_PARSE_SAPI or SPF_PARSE_SSML;
- SPF_VOICE_MASK = SPF_ASYNC or SPF_PURGEBEFORESPEAK or SPF_IS_FILENAME
- or SPF_IS_XML or SPF_IS_NOT_XML or SPF_NLP_MASK
- or SPF_PERSIST_XML or SPF_PARSE_MASK;
- SPF_UNUSED_FLAGS = not SPF_VOICE_MASK;
-
-type
- { IgoSpeechToText implementation }
- TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
- {$REGION 'Internal Declarations'}
- private
- FVoice: ISpVoice;
- protected
- { IgoTextToSpeech }
- function Speak(const AText: String): Boolean; override;
- procedure Stop; override;
- function IsSpeaking: Boolean; override;
- private
- class procedure VoiceCallback(wParam: WPARAM; lParam: LPARAM); stdcall; static;
- procedure HandleVoiceEvent;
- {$ENDREGION 'Internal Declarations'}
- public
- constructor Create;
- destructor Destroy; override;
- end;
-
-implementation
-
-uses
- System.Win.ComObj;
-
-{ These constants and functions come from sapi53.h from the Windows SDK }
-
-const
- SPFEI_FLAGCHECK = (UInt64(1) shl SPEI_RESERVED1) or (UInt64(1) shl SPEI_RESERVED2);
-
-function SPFEI(const AFlag: Longword): UInt64; inline;
-begin
- Result := (UInt64(1) shl AFlag) or SPFEI_FLAGCHECK;
-end;
-
-{ TgoTextToSpeechImplementation }
-
-constructor TgoTextToSpeechImplementation.Create;
-var
- Events: ULONGLONG;
-begin
- inherited Create;
- FVoice := CreateComObject(CLASS_SpVoice) as ISpVoice;
- if (FVoice <> nil) then
- begin
- { We want to be notified when speech synthesis has started and when it
- has stopped. }
- Events := SPFEI(SPEI_START_INPUT_STREAM) or SPFEI(SPEI_END_INPUT_STREAM);
-
- { Tell speech API what events we are interested in.
- * The first parameter tells the events we want to be notified about.
- * The second parameter tells which events should be queued in the event
- queue, so we can extract them later with GetEvents. We pass the same
- events here since we need to know which events were fired. }
- OleCheck(FVoice.SetInterest(Events, Events));
-
- { Tell speech API how to notify us. We use a callback mechanism here. }
- OleCheck(FVoice.SetNotifyCallbackFunction(VoiceCallback, 0, NativeInt(Self)));
-
- Available := True;
- end;
-end;
-
-destructor TgoTextToSpeechImplementation.Destroy;
-begin
- if (FVoice <> nil) then
- begin
- { Remove callback to make sure this object doesn't get called anymore. }
- FVoice.SetNotifyCallbackFunction(nil, 0, 0);
-
- { According to MSDN documentation, we need to (also) call this to unregister
- the callback. }
- FVoice.SetNotifySink(nil);
- end;
- inherited;
-end;
-
-procedure TgoTextToSpeechImplementation.HandleVoiceEvent;
-var
- Event: SPEVENT;
- NumEvents: ULONG;
-begin
- if (FVoice = nil) then
- Exit;
-
- { Handle all events in the event queue.
- Before calling GetEvents, the Event record should be cleared. }
- FillChar(Event, SizeOf(Event), 0);
- while (FVoice.GetEvents(1, @Event, NumEvents) = S_OK) do
- begin
- case Event.eEventId of
- SPEI_START_INPUT_STREAM:
- DoSpeechStarted;
-
- SPEI_END_INPUT_STREAM:
- DoSpeechFinished;
- end;
-
- FillChar(Event, SizeOf(Event), 0);
- end;
-end;
-
-function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
-var
- Status: SPVOICESTATUS;
-begin
- if (FVoice = nil) or (FVoice.GetStatus(Status, nil) <> S_OK) then
- Result := False
- else
- Result := ((Status.dwRunningState and SPRS_IS_SPEAKING) <> 0)
- and ((Status.dwRunningState and SPRS_DONE) = 0);
-end;
-
-function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
-begin
- if (FVoice = nil) then
- Result := False
- else
- Result := (FVoice.Speak(PWideChar(AText), SPF_ASYNC, nil) = S_OK);
-end;
-
-procedure TgoTextToSpeechImplementation.Stop;
-var
- NumSkipped: ULONG;
-begin
- if (FVoice <> nil) then
- FVoice.Skip('SENTENCE', MaxInt, NumSkipped);
-end;
-
-class procedure TgoTextToSpeechImplementation.VoiceCallback(wParam: WPARAM;
- lParam: LPARAM);
-begin
- Assert(lParam <> 0);
- Assert(TObject(lParam) is TgoTextToSpeechImplementation);
- TgoTextToSpeechImplementation(lParam).HandleVoiceEvent;
-end;
-
-end.
+unit Grijjy.TextToSpeech.Windows;
+{< Text To Speech engine implementation for Windows }
+
+interface
+
+uses
+ Winapi.Windows,
+ Winapi.ActiveX,
+
+ System.Variants,
+ System.SysUtils,
+
+ System.Classes, //TStrings
+ Grijjy.TextToSpeech,
+ Grijjy.TextToSpeech.Base;
+
+{ Partial import of sapi.dll type library }
+
+const
+ CLASS_SpVoice: TGUID = '{96749377-3391-11D2-9EE3-00C04F797396}';
+
+type
+ SPVISEMES = TOleEnum;
+ SPVPRIORITY = TOleEnum;
+ SPEVENTENUM = TOleEnum;
+
+type
+ {$ALIGN 8}
+ PSPEVENT = ^SPEVENT;
+ SPEVENT = record
+ eEventId: Word;
+ elParamType: Word;
+ ulStreamNum: ULONG;
+ ullAudioStreamOffset: ULONGLONG;
+ wParam: WPARAM;
+ lParam: LPARAM;
+ end;
+
+type
+ {$ALIGN 8}
+ PSPEVENTSOURCEINFO = ^SPEVENTSOURCEINFO;
+ SPEVENTSOURCEINFO = record
+ ullEventInterest: ULONGLONG;
+ ullQueuedInterest: ULONGLONG;
+ ulCount: ULONG;
+ end;
+
+type
+ {$ALIGN 4}
+ PSPVOICESTATUS = ^SPVOICESTATUS;
+ SPVOICESTATUS = record
+ ulCurrentStream: ULONG;
+ ulLastStreamQueued: ULONG;
+ hrLastResult: HResult;
+ dwRunningState: LongWord;
+ ulInputWordPos: ULONG;
+ ulInputWordLen: ULONG;
+ ulInputSentPos: ULONG;
+ ulInputSentLen: ULONG;
+ lBookmarkId: LONG;
+ PhonemeId: WideChar;
+ VisemeId: SPVISEMES;
+ dwReserved1: LongWord;
+ dwReserved2: LongWord;
+ end;
+
+type
+ SPNOTIFYCALLBACK = procedure (wParam: WPARAM; lParam: LPARAM); stdcall;
+
+type
+ // *********************************************************************//
+ // Interface: ISpNotifySink
+ // Flags: (512) Restricted
+ // GUID: {259684DC-37C3-11D2-9603-00C04F8EE628}
+ // *********************************************************************//
+ ISpNotifySink = interface(IUnknown)
+ ['{259684DC-37C3-11D2-9603-00C04F8EE628}']
+ function Notify: HResult; stdcall;
+ end;
+
+type
+ // *********************************************************************//
+ // Interface: ISpNotifySource
+ // Flags: (512) Restricted
+ // GUID: {5EFF4AEF-8487-11D2-961C-00C04F8EE628}
+ // *********************************************************************//
+ ISpNotifySource = interface(IUnknown)
+ ['{5EFF4AEF-8487-11D2-961C-00C04F8EE628}']
+ function SetNotifySink(const pNotifySink: ISpNotifySink): HResult; stdcall;
+ function SetNotifyWindowMessage(hWnd: HWND; Msg: UINT; wParam: WPARAM;
+ lParam: LPARAM): HResult; stdcall;
+ function SetNotifyCallbackFunction(pfnCallback: SPNOTIFYCALLBACK;
+ wParam: WPARAM; lParam: LPARAM): HResult; stdcall;
+ function SetNotifyCallbackInterface(pSpCallback: Pointer;
+ wParam: WPARAM; lParam: LPARAM): HResult; stdcall;
+ function SetNotifyWin32Event: HResult; stdcall;
+ function WaitForNotifyEvent(dwMilliseconds: LongWord): HResult; stdcall;
+ function GetNotifyEventHandle: Pointer; stdcall;
+ end;
+
+type
+ // *********************************************************************//
+ // Interface: ISpEventSource
+ // Flags: (512) Restricted
+ // GUID: {BE7A9CCE-5F9E-11D2-960F-00C04F8EE628}
+ // *********************************************************************//
+ ISpEventSource = interface(ISpNotifySource)
+ ['{BE7A9CCE-5F9E-11D2-960F-00C04F8EE628}']
+ function SetInterest(ullEventInterest: ULONGLONG; ullQueuedInterest: ULONGLONG): HResult; stdcall;
+ function GetEvents(ulCount: ULONG; pEventArray: PSPEVENT; out pulFetched: ULONG): HResult; stdcall;
+ function GetInfo(out pInfo: SPEVENTSOURCEINFO): HResult; stdcall;
+ end;
+
+
+type // Om: added
+// for code from https://edn.embarcadero.com/article/29583#EnumVoices
+
+
+// *********************************************************************//
+// Interface: ISpeechObjectToken
+// Flags: (4416) Dual OleAutomation Dispatchable
+// GUID: {C74A3ADC-B727-4500-A84A-B526721C8B8C}
+// *********************************************************************//
+ ISpeechObjectToken = interface(IDispatch) //only using description
+ ['{C74A3ADC-B727-4500-A84A-B526721C8B8C}']
+ // function Get_Id: WideString; safecall;
+ // function Get_DataKey: ISpeechDataKey; safecall;
+ // function Get_Category: ISpeechObjectTokenCategory; safecall;
+ function GetDescription(Locale: Integer): WideString; safecall;
+ // procedure SetId(const Id: WideString; const CategoryID: WideString; CreateIfNotExist: WordBool); safecall;
+ // function GetAttribute(const AttributeName: WideString): WideString; safecall;
+ // function CreateInstance(const pUnkOuter: IUnknown; ClsContext: SpeechTokenContext): IUnknown; safecall;
+ // procedure Remove(const ObjectStorageCLSID: WideString); safecall;
+ // function GetStorageFileName(const ObjectStorageCLSID: WideString; const KeyName: WideString;
+ // const FileName: WideString; Folder: SpeechTokenShellFolder): WideString; safecall;
+ // procedure RemoveStorageFileName(const ObjectStorageCLSID: WideString;
+ // const KeyName: WideString; DeleteFile: WordBool); safecall;
+ // function IsUISupported(const TypeOfUI: WideString; const ExtraData: OleVariant;
+ // const Object_: IUnknown): WordBool; safecall;
+ // procedure DisplayUI(hWnd: Integer; const Title: WideString; const TypeOfUI: WideString;
+ // const ExtraData: OleVariant; const Object_: IUnknown); safecall;
+ // function MatchesAttributes(const Attributes: WideString): WordBool; safecall;
+ // property Id: WideString read Get_Id;
+ // property DataKey: ISpeechDataKey read Get_DataKey;
+ // property Category: ISpeechObjectTokenCategory read Get_Category;
+ end;
+
+// Om: added
+// *********************************************************************//
+// Interface: ISpeechObjectTokens
+// Flags: (4416) Dual OleAutomation Dispatchable
+// GUID: {9285B776-2E7B-4BC0-B53E-580EB6FA967F}
+// *********************************************************************//
+ ISpeechObjectTokens = interface(IDispatch)
+ ['{9285B776-2E7B-4BC0-B53E-580EB6FA967F}']
+ function Get_Count: Integer; safecall;
+ function Item(Index: Integer): ISpeechObjectToken; safecall;
+ //function Get__NewEnum: IUnknown; safecall;
+ property Count: Integer read Get_Count;
+ //property _NewEnum: IUnknown read Get__NewEnum;
+ end;
+
+type
+ // *********************************************************************//
+ // Interface: ISpVoice
+ // Flags: (512) Restricted
+ // GUID: {6C44DF74-72B9-4992-A1EC-EF996E0422D4}
+ // *********************************************************************//
+ ISpVoice = interface(ISpEventSource)
+ ['{6C44DF74-72B9-4992-A1EC-EF996E0422D4}']
+ function SetOutput(const pUnkOutput: IUnknown;
+ fAllowFormatChanges: BOOL): HResult; stdcall;
+ function GetOutputObjectToken(out ppObjectToken: IUnknown): HResult; stdcall;
+ function GetOutputStream(out ppStream: IUnknown): HResult; stdcall;
+ function Pause: HResult; stdcall;
+ function Resume: HResult; stdcall;
+ function SetVoice(const pToken: IUnknown): HResult; stdcall;
+ function GetVoice(out ppToken: IUnknown): HResult; stdcall;
+ function Speak(pwcs: LPCWSTR; dwFlags: LongWord;
+ pulStreamNumber: PULONG): HResult; stdcall;
+ function SpeakStream(const pStream: IUnknown; dwFlags: LongWord;
+ out pulStreamNumber: LongWord): HResult; stdcall;
+ function GetStatus(out pStatus: SPVOICESTATUS;
+ ppszLastBookmark: PPWideChar): HResult; stdcall;
+ function Skip(pItemType: LPCWSTR; lNumItems: Integer;
+ out pulNumSkipped: ULONG): HResult; stdcall;
+ function SetPriority(ePriority: SPVPRIORITY): HResult; stdcall;
+ function GetPriority(out pePriority: SPVPRIORITY): HResult; stdcall;
+ function SetAlertBoundary(eBoundary: SPEVENTENUM): HResult; stdcall;
+ function GetAlertBoundary(out peBoundary: SPEVENTENUM): HResult; stdcall;
+ function SetRate(RateAdjust: Integer): HResult; stdcall;
+ function GetRate(out pRateAdjust: Integer): HResult; stdcall;
+ function SetVolume(usVolume: Word): HResult; stdcall;
+ function GetVolume(out pusVolume: Word): HResult; stdcall;
+ function WaitUntilDone(msTimeout: LongWord): HResult; stdcall;
+ function SetSyncSpeakTimeout(msTimeout: LongWord): HResult; stdcall;
+ function GetSyncSpeakTimeout(out pmsTimeout: LongWord): HResult; stdcall;
+ function SpeakCompleteEvent: Pointer; stdcall;
+ function IsUISupported(pszTypeOfUI: PWideChar; pvExtraData: Pointer;
+ cbExtraData: LongWord; out pfSupported: Integer): HResult; stdcall;
+ function DisplayUI(hWndParent: HWND; pszTitle: PWideChar;
+ pszTypeOfUI: PWideChar; pvExtraData: Pointer;
+ cbExtraData: LongWord): HResult; stdcall;
+ end;
+
+
+const
+ // SPEVENTENUM values
+ SPEI_UNDEFINED = $00000000;
+ SPEI_START_INPUT_STREAM = $00000001;
+ SPEI_END_INPUT_STREAM = $00000002;
+ SPEI_VOICE_CHANGE = $00000003;
+ SPEI_TTS_BOOKMARK = $00000004;
+ SPEI_WORD_BOUNDARY = $00000005;
+ SPEI_PHONEME = $00000006;
+ SPEI_SENTENCE_BOUNDARY = $00000007;
+ SPEI_VISEME = $00000008;
+ SPEI_TTS_AUDIO_LEVEL = $00000009;
+ SPEI_TTS_PRIVATE = $0000000F;
+ SPEI_MIN_TTS = $00000001;
+ SPEI_MAX_TTS = $0000000F;
+ SPEI_END_SR_STREAM = $00000022;
+ SPEI_SOUND_START = $00000023;
+ SPEI_SOUND_END = $00000024;
+ SPEI_PHRASE_START = $00000025;
+ SPEI_RECOGNITION = $00000026;
+ SPEI_HYPOTHESIS = $00000027;
+ SPEI_SR_BOOKMARK = $00000028;
+ SPEI_PROPERTY_NUM_CHANGE = $00000029;
+ SPEI_PROPERTY_STRING_CHANGE = $0000002A;
+ SPEI_FALSE_RECOGNITION = $0000002B;
+ SPEI_INTERFERENCE = $0000002C;
+ SPEI_REQUEST_UI = $0000002D;
+ SPEI_RECO_STATE_CHANGE = $0000002E;
+ SPEI_ADAPTATION = $0000002F;
+ SPEI_START_SR_STREAM = $00000030;
+ SPEI_RECO_OTHER_CONTEXT = $00000031;
+ SPEI_SR_AUDIO_LEVEL = $00000032;
+ SPEI_SR_RETAINEDAUDIO = $00000033;
+ SPEI_SR_PRIVATE = $00000034;
+ SPEI_ACTIVE_CATEGORY_CHANGED = $00000035;
+ SPEI_RESERVED5 = $00000036;
+ SPEI_RESERVED6 = $00000037;
+ SPEI_MIN_SR = $00000022;
+ SPEI_MAX_SR = $00000037;
+ SPEI_RESERVED1 = $0000001E;
+ SPEI_RESERVED2 = $00000021;
+ SPEI_RESERVED3 = $0000003F;
+
+const
+ // SPRUNSTATE flags
+ SPRS_DONE = 1 shl 0;
+ SPRS_IS_SPEAKING = 1 shl 1;
+
+const
+ // SPEAKFLAGS flags
+ SPF_DEFAULT = 0;
+ SPF_ASYNC = 1 shl 0;
+ SPF_PURGEBEFORESPEAK = 1 shl 1;
+ SPF_IS_FILENAME = 1 shl 2;
+ SPF_IS_XML = 1 shl 3;
+ SPF_IS_NOT_XML = 1 shl 4;
+ SPF_PERSIST_XML = 1 shl 5;
+ SPF_NLP_SPEAK_PUNC = 1 shl 6;
+ SPF_PARSE_SAPI = 1 shl 7;
+ SPF_PARSE_SSML = 1 shl 8;
+ SPF_PARSE_AUTODETECT = 0;
+ SPF_NLP_MASK = SPF_NLP_SPEAK_PUNC;
+ SPF_PARSE_MASK = SPF_PARSE_SAPI or SPF_PARSE_SSML;
+ SPF_VOICE_MASK = SPF_ASYNC or SPF_PURGEBEFORESPEAK or SPF_IS_FILENAME
+ or SPF_IS_XML or SPF_IS_NOT_XML or SPF_NLP_MASK
+ or SPF_PERSIST_XML or SPF_PARSE_MASK;
+ SPF_UNUSED_FLAGS = not SPF_VOICE_MASK;
+
+type
+ { IgoSpeechToText implementation }
+ TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
+ {$REGION 'Internal Declarations'}
+ private
+ FVoice: ISpVoice;
+
+ fCOMVoice: OLEVariant;
+ protected
+ { IgoTextToSpeech }
+ function getVoices(aList:TStrings):boolean; override; // Om: mar20: get list of available voices ( only for iOS at this time)
+ function getVoiceGender:TVoiceGender; override; // Om: mar20:
+ function setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; override; // Om: mar20: set voice w/ spec like 'pt-BR'
+
+
+ function Speak(const AText: String): Boolean; override;
+ procedure Stop; override;
+ function IsSpeaking: Boolean; override;
+ private
+
+ class procedure VoiceCallback(wParam: WPARAM; lParam: LPARAM); stdcall; static;
+ procedure HandleVoiceEvent;
+ procedure getNativeVoices;
+ {$ENDREGION 'Internal Declarations'}
+ public
+ fNativeVoice :OLEVariant; //male and female voices
+ fMaleVoice :OLEVariant;
+ fFemaleVoice :OLEVariant;
+
+ constructor Create;
+ destructor Destroy; override;
+ end;
+
+implementation //----------------------------------------------------------------------------
+
+uses
+ System.Win.ComObj;
+
+{ These constants and functions come from sapi53.h from the Windows SDK }
+
+const
+ SPFEI_FLAGCHECK = (UInt64(1) shl SPEI_RESERVED1) or (UInt64(1) shl SPEI_RESERVED2);
+
+function SPFEI(const AFlag: Longword): UInt64; inline;
+begin
+ Result := (UInt64(1) shl AFlag) or SPFEI_FLAGCHECK;
+end;
+
+{ TgoTextToSpeechImplementation }
+
+constructor TgoTextToSpeechImplementation.Create;
+var
+ Events: ULONGLONG;
+begin
+ inherited Create;
+
+ fNativeVoice := varNull;
+ fMaleVoice := varNull;
+ fFemaleVoice := varNull;
+
+ //
+ FVoice := CreateComObject(CLASS_SpVoice) as ISpVoice;
+
+ if (FVoice <> nil) then
+ begin
+ { We want to be notified when speech synthesis has started and when it
+ has stopped. }
+ Events := SPFEI(SPEI_START_INPUT_STREAM) or SPFEI(SPEI_END_INPUT_STREAM);
+
+ { Tell speech API what events we are interested in.
+ * The first parameter tells the events we want to be notified about.
+ * The second parameter tells which events should be queued in the event
+ queue, so we can extract them later with GetEvents. We pass the same
+ events here since we need to know which events were fired. }
+ OleCheck(FVoice.SetInterest(Events, Events));
+
+ { Tell speech API how to notify us. We use a callback mechanism here. }
+ OleCheck(FVoice.SetNotifyCallbackFunction(VoiceCallback, 0, NativeInt(Self)));
+
+ fCOMVoice := CreateOLEObject('SAPI.SpVoice'); //use OLE auto to get voices
+
+ getNativeVoices; //Om:
+
+ Available := True;
+ end
+ else fCOMVoice := varNull; //?? no voice
+end;
+
+destructor TgoTextToSpeechImplementation.Destroy;
+begin
+ if (FVoice <> nil) then
+ begin
+ { Remove callback to make sure this object doesn't get called anymore. }
+ FVoice.SetNotifyCallbackFunction(nil, 0, 0);
+
+ { According to MSDN documentation, we need to (also) call this to unregister
+ the callback. }
+ FVoice.SetNotifySink(nil);
+ end;
+ inherited;
+end;
+
+procedure TgoTextToSpeechImplementation.HandleVoiceEvent;
+var
+ Event: SPEVENT;
+ NumEvents: ULONG;
+begin
+ if (FVoice = nil) then
+ Exit;
+
+ { Handle all events in the event queue.
+ Before calling GetEvents, the Event record should be cleared. }
+ FillChar(Event, SizeOf(Event), 0);
+ while (FVoice.GetEvents(1, @Event, NumEvents) = S_OK) do
+ begin
+ case Event.eEventId of
+ SPEI_START_INPUT_STREAM:
+ DoSpeechStarted;
+
+ SPEI_END_INPUT_STREAM:
+ DoSpeechFinished;
+ end;
+
+ FillChar(Event, SizeOf(Event), 0);
+ end;
+end;
+
+function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
+var
+ Status: SPVOICESTATUS;
+begin
+ if (FVoice = nil) or (FVoice.GetStatus(Status, nil) <> S_OK) then Result := False
+ else Result := ((Status.dwRunningState and SPRS_IS_SPEAKING) <> 0) and ((Status.dwRunningState and SPRS_DONE) = 0);
+end;
+
+procedure TgoTextToSpeechImplementation.getNativeVoices; // Om: mar20: get list of available voices ( only for iOS at this time)
+var
+ i: Integer;
+ s:String;
+ vozes:OLEVariant;
+ aVoiceToken:OLEVariant;
+begin
+ fNativeVoice := varNull;
+ fMaleVoice := varNull;
+ fFemaleVoice := varNull;
+
+ if not VarIsEmpty( fCOMVoice ) then
+ begin
+ vozes := fCOMVoice.getVoices;
+ for i := 0 to vozes.Count - 1 do
+ begin
+ aVoiceToken := vozes.item(i);
+ s := Lowercase(aVoiceToken.GetDescription);
+ // locate the best male and female voices available and memoise objects
+ if Pos('portuguese',s)>0 then
+ begin
+ fFemaleVoice := aVoiceToken;
+ //aVoiceToken.AddRef; // <-- necessary ??
+ end
+ else if Pos('english',s)>0 then
+ begin
+ fMaleVoice := aVoiceToken;
+ //aVoiceToken.AddRef;
+ end;
+ end;
+ end;
+
+ if not VarIsNull(fMaleVoice) then
+ fNativeVoice := fMaleVoice; //any voice will do, but..
+ if not VarIsNull(fFemaleVoice) then
+ fNativeVoice := fFemaleVoice; //.. default = female
+end;
+
+// from https://edn.embarcadero.com/article/29583#EnumVoices
+function TgoTextToSpeechImplementation.getVoices(aList:TStrings):boolean; // Om: mar20: get list of available voices ( only for iOS at this time)
+var
+ i: Integer; S:String;
+ SOToken: OLEVariant; //ISpeechObjectToken;
+ SOTokens: OLEVariant; //ISpeechObjectTokens;
+begin
+ // fVoice..EventInterests := SVEAllEvents;
+ //Log('About to enumerate voices');
+ Result := false;
+
+ if VarIsNull(fCOMVoice) then exit; //sanity check
+
+ SOTokens := fCOMVoice.GetVoices('', ''); //
+ for I := 0 to SOTokens.Count - 1 do
+ begin
+ //For each voice, store the descriptor in the TStrings list
+ SOToken := SOTokens.Item(I);
+ S := SOToken.GetDescription(0);
+ aList.Add(S);
+ // cbVoices.Items.AddObject(SOToken.GetDescription(0), TObject(SOToken));
+ //Increment descriptor reference count to ensure it's not destroyed
+ // SOToken._AddRef;
+ Result := true;
+ end;
+
+ // aList.Add('------------------------');
+ // aList.Add(fMaleVoice.GetDescription); //test show saved voices
+ // aList.Add(fFemaleVoice.GetDescription);
+
+ // if cbVoices.Items.Count > 0 then
+ // begin
+ // cbVoices.ItemIndex := 0; //Select 1st voice
+ // cbVoices.OnChange(cbVoices); //& ensure OnChange triggers
+ // end;
+ // Log('Enumerated voices');
+ // Log('About to check attributes');
+ // tbRate.Position := SpVoice.Rate;
+ // lblRate.Caption := IntToStr(tbRate.Position);
+ // tbVolume.Position := SpVoice.Volume;
+ // lblVolume.Caption := IntToStr(tbVolume.Position);
+ // Log('Checked attributes');
+ //
+end;
+
+
+// getVoices() using dispatch interfaces
+// var
+// i: Integer;
+// s:String;
+// voz:OLEVariant;
+// vozes:OLEVariant;
+// aVoiceToken:OLEVariant;
+//
+// begin
+// Result := false;
+// if Assigned(fVoice) then
+// begin
+// voz := CreateOLEObject('SAPI.SpVoice'); //use OLE auto to get voices
+// if not VarIsEmpty( voz ) then
+// begin
+// vozes := voz.getVoices;
+// for i := 0 to vozes.Count - 1 do
+// begin
+// aVoiceToken := vozes.item(i);
+// s := aVoiceToken.GetDescription;
+// aList.Add( s );
+// Result := true;
+// end;
+// end;
+// end;
+// end;
+
+// Om: mar20:
+
+function TgoTextToSpeechImplementation.getVoiceGender:TVoiceGender; // Om: mar20:
+begin
+ Result := vgUnkown; // not implemented for windows yet
+ // if not VarIsNull(fNativeVoice) then
+ // begin
+ // if (not VarIsNull(fFemaleVoice) ) and (fNativeVoice.getDescription=fFemaleVoice.getDescription) then Result := vgFemale
+ // else if (not VarIsNull(fMaleVoice) ) and (fNativeVoice.getDescription=fMaleVoice.getDescription) then Result := vgFemale;
+ // end;
+end;
+
+function TgoTextToSpeechImplementation.setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String ):boolean; // Om: mar20: set voice w/ spec like 'pt-BR'
+begin
+ Result := false; // not implemented
+ //TODO:
+end;
+
+function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
+var s:String;
+begin
+ if (FVoice = nil) then Result := False
+ else begin
+ // // alternating male-female voices
+ // if not ( VarIsNull(fFemaleVoice) or VarIsNull(fMaleVoice) ) then //
+ // begin
+ // if (fNativeVoice.getDescription=fFemaleVoice.getDescription) then fNativeVoice:=fMaleVoice
+ // else fNativeVoice:=fFemaleVoice;
+ // end;
+
+ // // commented voice selection. Neither of the SetVoice calls work
+
+ // // set voice
+ // if not ( VarIsNull(fNativeVoice) or VarIsNull(fCOMVoice)) then
+ // begin
+ // //fCOMVoice.SetVoice(fNativeVoice); //nem um dos jeitos funcionou, desabilitei
+ // fVoice.SetVoice( fNativeVoice ); // this breaks the code
+ // end;
+
+
+ Result := ( fVoice.Speak( PWideChar(AText), SPF_ASYNC, nil) = S_OK ); // do speak
+
+ // if not VarIsNull(fNativeVoice) then //test
+ // begin
+ // s:= fNativeVoice.getDescription;
+ // Result := (FVoice.Speak( PWideChar(s), SPF_ASYNC, nil) = S_OK);
+ // end;
+
+ end;
+end;
+
+procedure TgoTextToSpeechImplementation.Stop;
+var
+ NumSkipped: ULONG;
+begin
+ if (FVoice <> nil) then
+ FVoice.Skip('SENTENCE', MaxInt, NumSkipped);
+end;
+
+class procedure TgoTextToSpeechImplementation.VoiceCallback(wParam: WPARAM;
+ lParam: LPARAM);
+begin
+ Assert(lParam <> 0);
+ Assert(TObject(lParam) is TgoTextToSpeechImplementation);
+ TgoTextToSpeechImplementation(lParam).HandleVoiceEvent;
+end;
+
+//-----------------------------------------------------------
+
+// from https://stackoverflow.com/questions/19369809/delphi-get-country-codes-by-localeid/37981772#37981772
+function LCIDToLocaleName(Locale: LCID; lpName: LPWSTR; cchName: Integer;
+ dwFlags: DWORD): Integer; stdcall;external kernel32 name 'LCIDToLocaleName';
+
+
+function LocaleIDString():string;
+var
+ strNameBuffer : array [0..255] of WideChar; // 84 was len from original process online
+ //localID : TLocaleID;
+ // localID was 0, so didn't initialize, but still returned proper code page.
+ // using 0 in lieu of localID : nets the same result, var not required.
+ i : integer;
+begin
+ Result := '';
+
+ // LOCALE_USER_DEFAULT vs. LOCALE_SYSTEM_DEFAULT
+ // since XP LOCALE_USER_DEFAULT is considered good practice for compatibility
+ if (LCIDToLocaleName(LOCALE_USER_DEFAULT, strNameBuffer, 255, 0) > 0) then
+ for i := 0 to 255 do
+ begin
+ if strNameBuffer[i] = #0 then break
+ else Result := Result + strNameBuffer[i];
+ end;
+
+ if (Length(Result) = 0) and (LCIDToLocaleName(0, strNameBuffer, 255, 0) > 0) then
+ for i := 0 to 255 do
+ begin
+ if strNameBuffer[i] = #0 then break
+ else Result := Result + strNameBuffer[i];
+ end;
+
+ if Length(Result) = 0 then
+ Result := 'NR-NR' // defaulting to [No Reply - No Reply]
+end;
+
+procedure getNativeVoiceLanguage;
+begin
+ NativeSpeechLanguage := LocaleIDString; // 'pt-BR'
+end;
+
+initialization
+ getNativeVoiceLanguage; // get OS language settings
+end.
diff --git a/TextToSpeech/Grijjy.TextToSpeech.iOS.pas b/TextToSpeech/Grijjy.TextToSpeech.iOS.pas
index 245579b..aaf918f 100644
--- a/TextToSpeech/Grijjy.TextToSpeech.iOS.pas
+++ b/TextToSpeech/Grijjy.TextToSpeech.iOS.pas
@@ -1,233 +1,510 @@
-unit Grijjy.TextToSpeech.iOS;
-{< Text To Speech engine implementation for iOS }
-
-interface
-
-uses
- Macapi.ObjectiveC,
- iOSapi.Foundation,
- iOSapi.CocoaTypes,
- iOSapi.AVFoundation,
- Grijjy.TextToSpeech.Base;
-
-{ These declarations are missing from iOSapi.AVFoundation }
-type
- AVSpeechBoundary = NSInteger;
-
-const
- AVSpeechBoundaryImmediate = 0;
- AVSpeechBoundaryWord = 1;
-
-type
- AVSpeechSynthesisVoice = interface;
-
- AVSpeechSynthesisVoiceClass = interface(NSObjectClass)
- ['{A2006345-086C-4416-AAE7-3B1DD6B47BE1}']
- {class} function speechVoices: NSArray{}; cdecl;
- {class} function currentLanguageCode: NSString; cdecl;
- {class} function voiceWithLanguage(language: NSString): AVSpeechSynthesisVoice; cdecl;
- end;
-
- AVSpeechSynthesisVoice = interface(NSObject)
- ['{FBFD24DF-08F6-43A3-8A9B-32D583B0B8B5}']
- function language: NSString; cdecl;
- end;
-
- TAVSpeechSynthesisVoice = class(TOCGenericImport) end;
-
-type
- AVSpeechUtterance = interface;
-
- AVSpeechUtteranceClass = interface(NSObjectClass)
- ['{E6695EAF-6909-4D1E-AFFA-DFB7CDC256EF}']
- {class} function speechUtteranceWithString(str: NSString): AVSpeechUtterance; cdecl;
- end;
-
- AVSpeechUtterance = interface(NSObject)
- ['{5D2DDD5B-688B-4193-B0F3-26C6C755AEDC}']
- function initWithString(str: NSString): AVSpeechUtterance; cdecl;
- function voice: AVSpeechSynthesisVoice; cdecl;
- procedure setVoice(voice: AVSpeechSynthesisVoice); cdecl;
- function speechString: NSString; cdecl;
- function rate: Single; cdecl;
- procedure setRate(rate: Single); cdecl;
- function pitchMultiplier: Single; cdecl;
- procedure setPitchMultiplier(pitchMultiplier: Single); cdecl;
- function volume: Single; cdecl;
- procedure setVolume(volume: Single); cdecl;
- function preUtteranceDelay: NSTimeInterval; cdecl;
- procedure setPreUtteranceDelay(preUtteranceDelay: NSTimeInterval); cdecl;
- function postUtteranceDelay: NSTimeInterval; cdecl;
- procedure setPostUtteranceDelay(postUtteranceDelay: NSTimeInterval); cdecl;
- end;
-
- TAVSpeechUtterance = class(TOCGenericImport) end;
-
-type
- AVSpeechSynthesizer = interface;
- AVSpeechSynthesizerDelegate = interface;
-
- AVSpeechSynthesizerClass = interface(NSObjectClass)
- ['{4F761699-0210-47EB-802B-DAC900C9979B}']
- end;
-
- AVSpeechSynthesizer = interface(NSObject)
- ['{EC1850A7-B7EA-4C5D-A47B-D3EDDC3D4146}']
- function delegate: Pointer; cdecl;
- procedure setDelegate(delegate: AVSpeechSynthesizerDelegate); cdecl;
- function isSpeaking: Boolean; cdecl;
- function isPaused: Boolean; cdecl;
- procedure speakUtterance(utterance: AVSpeechUtterance); cdecl;
- function stopSpeakingAtBoundary(boundary: AVSpeechBoundary): Boolean; cdecl;
- function pauseSpeakingAtBoundary(boundary: AVSpeechBoundary): Boolean; cdecl;
- function continueSpeaking: Boolean; cdecl;
- end;
-
- TAVSpeechSynthesizer = class(TOCGenericImport) end;
-
- AVSpeechSynthesizerDelegate = interface(IObjectiveC)
- ['{EF579B2B-6CB1-47E4-AD77-07F580876F8F}']
- [MethodName('speechSynthesizer:didStartSpeechUtterance:')]
- procedure speechSynthesizerDidStartSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
-
- [MethodName('speechSynthesizer:didFinishSpeechUtterance:')]
- procedure speechSynthesizerDidFinishSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
-
- [MethodName('speechSynthesizer:didCancelSpeechUtterance:')]
- procedure speechSynthesizerDidCancelSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
- end;
-
-type
- { IgoSpeechToText implementation }
- TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
- {$REGION 'Internal Declarations'}
- private const
- { AVSpeechUtterance.Rate ranges from 0.0 to 1.0, where 0.5 is the default.
- On iOS 9 (and up?), the default right is fine.
- On iOS 8 and earlier, it is much too fast. }
- DEFAULT_SPEECH_RATE_IOS8_DOWN = 0.1;
- private type
- TDelegate = class(TOCLocal, AVSpeechSynthesizerDelegate)
- private
- [weak] FTextToSpeech: TgoTextToSpeechImplementation;
- FFireEvents: Boolean;
- public
- constructor Create(const ATextToSpeech: TgoTextToSpeechImplementation);
- public
- { AVSpeechSynthesizerDelegate }
- [MethodName('speechSynthesizer:didStartSpeechUtterance:')]
- procedure speechSynthesizerDidStartSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
-
- [MethodName('speechSynthesizer:didFinishSpeechUtterance:')]
- procedure speechSynthesizerDidFinishSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
-
- [MethodName('speechSynthesizer:didCancelSpeechUtterance:')]
- procedure speechSynthesizerDidCancelSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
- end;
- private
- FSpeechSynthesizer: AVSpeechSynthesizer;
- FDelegate: TDelegate;
- protected
- { IgoTextToSpeech }
- function Speak(const AText: String): Boolean; override;
- procedure Stop; override;
- function IsSpeaking: Boolean; override;
- {$ENDREGION 'Internal Declarations'}
- public
- constructor Create;
- destructor Destroy; override;
- end;
-
-implementation
-
-uses
- System.SysUtils,
- Macapi.Helpers;
-
-{ TgoTextToSpeechImplementation }
-
-constructor TgoTextToSpeechImplementation.Create;
-begin
- inherited;
- FSpeechSynthesizer := TAVSpeechSynthesizer.Create;
- FDelegate := TgoTextToSpeechImplementation.TDelegate.Create(Self);
- FSpeechSynthesizer.setDelegate(FDelegate);
- Available := True;
-end;
-
-destructor TgoTextToSpeechImplementation.Destroy;
-begin
- if (FSpeechSynthesizer <> nil) then
- FSpeechSynthesizer.release;
- inherited;
-end;
-
-function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
-begin
- Result := FSpeechSynthesizer.isSpeaking;
-end;
-
-function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
-var
- Utterance: AVSpeechUtterance;
-begin
- if (AText.Trim = '') then
- Exit(True);
-
- if (FSpeechSynthesizer.isSpeaking) then
- begin
- { Calling stopSpeakingAtBoundary will also call
- speechSynthesizerDidCancelSpeechUtterance at some point. We don't want
- that event to fire here, so we set FFireEvents to False. That flag is
- set to True again when the next speech is started. }
- FDelegate.FFireEvents := False;
- FSpeechSynthesizer.stopSpeakingAtBoundary(AVSpeechBoundaryImmediate);
- end;
-
- Utterance := TAVSpeechUtterance.OCClass.speechUtteranceWithString(StrToNSStr(AText));
- if (not TOSVersion.Check(9)) then
- Utterance.setRate(DEFAULT_SPEECH_RATE_IOS8_DOWN);
- FSpeechSynthesizer.speakUtterance(Utterance);
- Result := True;
-end;
-
-procedure TgoTextToSpeechImplementation.Stop;
-begin
- if (FSpeechSynthesizer.isSpeaking) then
- { This will also call speechSynthesizerDidCancelSpeechUtterance }
- FSpeechSynthesizer.stopSpeakingAtBoundary(AVSpeechBoundaryImmediate);
-end;
-
-{ TgoTextToSpeechImplementation.TDelegate }
-
-constructor TgoTextToSpeechImplementation.TDelegate.Create(
- const ATextToSpeech: TgoTextToSpeechImplementation);
-begin
- Assert(Assigned(ATextToSpeech));
- inherited Create;
- FTextToSpeech := ATextToSpeech;
-end;
-
-procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidCancelSpeechUtterance(
- synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
-begin
- if Assigned(FTextToSpeech) and (FFireEvents) then
- FTextToSpeech.DoSpeechFinished;
-end;
-
-procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidFinishSpeechUtterance(
- synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
-begin
- if Assigned(FTextToSpeech) and (FFireEvents) then
- FTextToSpeech.DoSpeechFinished;
-end;
-
-procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidStartSpeechUtterance(
- synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
-begin
- FFireEvents := True;
- if Assigned(FTextToSpeech) then
- FTextToSpeech.DoSpeechStarted;
-end;
-
-end.
+unit Grijjy.TextToSpeech.iOS;
+{< Text To Speech engine implementation for iOS }
+
+interface
+
+uses
+ System.Classes, //Om: for TStrings
+ Macapi.ObjectiveC,
+ iOSapi.Foundation,
+ iOSapi.CocoaTypes,
+ iOSapi.AVFoundation,
+ Grijjy.TextToSpeech,
+ Grijjy.TextToSpeech.Base;
+
+{ These declarations are missing from iOSapi.AVFoundation }
+type
+ AVSpeechBoundary = NSInteger;
+
+const
+ AVSpeechBoundaryImmediate = 0;
+ AVSpeechBoundaryWord = 1;
+
+type
+ AVSpeechSynthesisVoice = interface;
+
+ AVSpeechSynthesisVoiceClass = interface(NSObjectClass)
+ ['{A2006345-086C-4416-AAE7-3B1DD6B47BE1}']
+ {class} function speechVoices: NSArray{}; cdecl;
+ {class} function currentLanguageCode: NSString; cdecl;
+ {class} function voiceWithLanguage(language: NSString): AVSpeechSynthesisVoice; cdecl;
+ end;
+
+ AVSpeechSynthesisVoice = interface(NSObject)
+ ['{FBFD24DF-08F6-43A3-8A9B-32D583B0B8B5}']
+ function language: NSString; cdecl;
+ // Om: added the fns below
+ function identifier: NSString; cdecl; //Om: from https://github.com/FMXExpress/ios-object-pascal-wrapper/blob/master/iOSapi.AVFoundation.pas
+ function name: NSString; cdecl; //Om:
+ end;
+
+ TAVSpeechSynthesisVoice = class(TOCGenericImport) end;
+
+type
+ AVSpeechUtterance = interface;
+
+ AVSpeechUtteranceClass = interface(NSObjectClass)
+ ['{E6695EAF-6909-4D1E-AFFA-DFB7CDC256EF}']
+ {class} function speechUtteranceWithString(str: NSString): AVSpeechUtterance; cdecl;
+ end;
+
+ AVSpeechUtterance = interface(NSObject)
+ ['{5D2DDD5B-688B-4193-B0F3-26C6C755AEDC}']
+ function initWithString(str: NSString): AVSpeechUtterance; cdecl;
+ function voice: AVSpeechSynthesisVoice; cdecl;
+ procedure setVoice(voice: AVSpeechSynthesisVoice); cdecl;
+ function speechString: NSString; cdecl;
+ function rate: Single; cdecl;
+ procedure setRate(rate: Single); cdecl;
+ function pitchMultiplier: Single; cdecl;
+ procedure setPitchMultiplier(pitchMultiplier: Single); cdecl;
+ function volume: Single; cdecl;
+ procedure setVolume(volume: Single); cdecl;
+ function preUtteranceDelay: NSTimeInterval; cdecl;
+ procedure setPreUtteranceDelay(preUtteranceDelay: NSTimeInterval); cdecl;
+ function postUtteranceDelay: NSTimeInterval; cdecl;
+ procedure setPostUtteranceDelay(postUtteranceDelay: NSTimeInterval); cdecl;
+ end;
+
+ TAVSpeechUtterance = class(TOCGenericImport) end;
+
+type
+ AVSpeechSynthesizer = interface;
+ AVSpeechSynthesizerDelegate = interface;
+
+ AVSpeechSynthesizerClass = interface(NSObjectClass)
+ ['{4F761699-0210-47EB-802B-DAC900C9979B}']
+ end;
+
+ AVSpeechSynthesizer = interface(NSObject)
+ ['{EC1850A7-B7EA-4C5D-A47B-D3EDDC3D4146}']
+ function delegate: Pointer; cdecl;
+ procedure setDelegate(delegate: AVSpeechSynthesizerDelegate); cdecl;
+ function isSpeaking: Boolean; cdecl;
+ function isPaused: Boolean; cdecl;
+ procedure speakUtterance(utterance: AVSpeechUtterance); cdecl;
+ function stopSpeakingAtBoundary(boundary: AVSpeechBoundary): Boolean; cdecl;
+ function pauseSpeakingAtBoundary(boundary: AVSpeechBoundary): Boolean; cdecl;
+ function continueSpeaking: Boolean; cdecl;
+ end;
+
+ TAVSpeechSynthesizer = class(TOCGenericImport) end;
+
+ AVSpeechSynthesizerDelegate = interface(IObjectiveC)
+ ['{EF579B2B-6CB1-47E4-AD77-07F580876F8F}']
+ [MethodName('speechSynthesizer:didStartSpeechUtterance:')]
+ procedure speechSynthesizerDidStartSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+
+ [MethodName('speechSynthesizer:didFinishSpeechUtterance:')]
+ procedure speechSynthesizerDidFinishSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+
+ [MethodName('speechSynthesizer:didCancelSpeechUtterance:')]
+ procedure speechSynthesizerDidCancelSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+ end;
+
+type
+ { IgoSpeechToText implementation }
+ TgoTextToSpeechImplementation = class(TgoTextToSpeechBase)
+ {$REGION 'Internal Declarations'}
+ private const
+ { AVSpeechUtterance.Rate ranges from 0.0 to 1.0, where 0.5 is the default.
+ On iOS 9 (and up?), the default right is fine.
+ On iOS 8 and earlier, it is much too fast. }
+ DEFAULT_SPEECH_RATE_IOS8_DOWN = 0.1;
+ private type
+ TDelegate = class(TOCLocal, AVSpeechSynthesizerDelegate)
+ private
+ [weak] FTextToSpeech: TgoTextToSpeechImplementation;
+ FFireEvents: Boolean;
+ public
+ constructor Create(const ATextToSpeech: TgoTextToSpeechImplementation);
+ public
+ { AVSpeechSynthesizerDelegate }
+ [MethodName('speechSynthesizer:didStartSpeechUtterance:')]
+ procedure speechSynthesizerDidStartSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+
+ [MethodName('speechSynthesizer:didFinishSpeechUtterance:')]
+ procedure speechSynthesizerDidFinishSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+
+ [MethodName('speechSynthesizer:didCancelSpeechUtterance:')]
+ procedure speechSynthesizerDidCancelSpeechUtterance(synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance); cdecl;
+ end;
+ private
+ FSpeechSynthesizer: AVSpeechSynthesizer;
+ FDelegate: TDelegate;
+
+ fNativeVoice:AVSpeechSynthesisVoice; //Om:
+ fMaleVoice, fFemaleVoice: AVSpeechSynthesisVoice;
+
+ protected
+ Procedure getNativeVoices;
+ { IgoTextToSpeech }
+ function getVoices(aList:TStrings):boolean; override; // Om: mar20: get list of available voices ( only for iOS at this time)
+ function getVoiceGender:TVoiceGender; override; // Om: mar20:
+ function setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; override; // Om: mar20: set voice w/ spec like 'pt-BR'
+
+ function Speak(const AText: String): Boolean; override;
+ procedure Stop; override;
+ function IsSpeaking: Boolean; override;
+ {$ENDREGION 'Internal Declarations'}
+
+ public
+ constructor Create;
+ destructor Destroy; override;
+ end;
+
+//function getDeviceCountryCode:String; //platform specific get country code
+//function getOSLanguage:String;
+
+
+implementation //---------------------------------------------------
+
+uses
+ System.SysUtils,
+ Macapi.Helpers;
+
+
+// On iOS I could not find a way to tell male from female voices, so I made this little
+// table with guesses :( Not sure about the genders for the names from exotic places
+Type
+ RVoiceGenderRec=record
+ Lang:String;
+ VoiceName:String;
+ Gender:Char;
+ end;
+
+const
+ Num_iOS_Voices=59;
+ iOSVoiceGenders:Array[0..Num_iOS_Voices-1] of RVoiceGenderRec=(
+ ( Lang: 'ar-SA'; VoiceName : 'Maged'; Gender:'f'),
+ ( Lang: 'cs-CZ'; VoiceName : 'Zuzana'; Gender:'f'),
+ ( Lang: 'da-DK'; VoiceName : 'Sara'; Gender:'f'),
+ ( Lang: 'de-DE'; VoiceName : 'Anna'; Gender:'f'),
+ ( Lang: 'de-DE'; VoiceName : 'Helena'; Gender:'f'),
+ ( Lang: 'de-DE'; VoiceName : 'Martin'; Gender:'m'),
+ ( Lang: 'el-GR'; VoiceName : 'Melina'; Gender:'f'),
+ ( Lang: 'en-AU'; VoiceName : 'Catherine'; Gender:'f'),
+ ( Lang: 'en-AU'; VoiceName : 'Gordon'; Gender:'m'),
+ ( Lang: 'en-AU'; VoiceName : 'Karen'; Gender:'f'),
+ ( Lang: 'en-GB'; VoiceName : 'Arthur'; Gender:'m'),
+ ( Lang: 'en-GB'; VoiceName : 'Daniel'; Gender:'m'),
+ ( Lang: 'en-GB'; VoiceName : 'Martha'; Gender:'f'),
+ ( Lang: 'en-IE'; VoiceName : 'Moira'; Gender:'f'),
+ ( Lang: 'en-IN'; VoiceName : 'Rishi'; Gender:'m'), //?
+ ( Lang: 'en-US'; VoiceName : 'Aaron'; Gender:'m'),
+ ( Lang: 'en-US'; VoiceName : 'Fred'; Gender:'m'),
+ ( Lang: 'en-US'; VoiceName : 'Nicky'; Gender:'f'),
+ ( Lang: 'en-US'; VoiceName : 'Samantha'; Gender:'f'),
+ ( Lang: 'en-ZA'; VoiceName : 'Tessa'; Gender:'f'),
+ ( Lang: 'es-ES'; VoiceName : 'Mónica'; Gender:'f'),
+ ( Lang: 'es-MX'; VoiceName : 'Paulina'; Gender:'f'),
+ ( Lang: 'fi-FI'; VoiceName : 'Satu'; Gender:'m'),
+ ( Lang: 'fr-CA'; VoiceName : 'Amélie'; Gender:'f'),
+ ( Lang: 'fr-FR'; VoiceName : 'Daniel'; Gender:'m'),
+ ( Lang: 'fr-FR'; VoiceName : 'Marie'; Gender:'f'),
+ ( Lang: 'fr-FR'; VoiceName : 'Thomas'; Gender:'m'),
+ ( Lang: 'he-IL'; VoiceName : 'Carmit'; Gender:'m'),
+ ( Lang: 'hi-IN'; VoiceName : 'Lekha'; Gender:'f'),
+ ( Lang: 'hu-HU'; VoiceName : 'Mariska'; Gender:'f'),
+ ( Lang: 'id-ID'; VoiceName : 'Damayantict'; Gender:'m'),
+ ( Lang: 'it-IT'; VoiceName : 'Alice'; Gender:'f'),
+ ( Lang: 'ja-JP'; VoiceName : 'Hattori'; Gender:'m'),
+ ( Lang: 'ja-JP'; VoiceName : 'Kyoko'; Gender:'f'),
+ ( Lang: 'ja-JP'; VoiceName : 'O-ren'; Gender:'m'),
+ ( Lang: 'ko-KR'; VoiceName : 'Yuna'; Gender:'f'),
+ ( Lang: 'nl-BE'; VoiceName : 'Ellen'; Gender:'f'),
+ ( Lang: 'nl-NL'; VoiceName : 'Xander'; Gender:'m'),
+ ( Lang: 'no-NO'; VoiceName : 'Nora'; Gender:'f'),
+ ( Lang: 'pl-PL'; VoiceName : 'Zosia'; Gender:'f'),
+ ( Lang: 'pt-BR'; VoiceName : 'Felipe (Aprimorado)'; Gender:'m'),
+ ( Lang: 'pt-BR'; VoiceName : 'Luciana (Aprimorado)'; Gender:'f'),
+ ( Lang: 'pt-BR'; VoiceName : 'Felipe'; Gender:'m'),
+ ( Lang: 'pt-BR'; VoiceName : 'Luciana'; Gender:'f'),
+ ( Lang: 'pt-PT'; VoiceName : 'Catarina (Aprimorado)'; Gender:'f'),
+ ( Lang: 'pt-PT'; VoiceName : 'Catarina'; Gender:'f'),
+ ( Lang: 'pt-PT'; VoiceName : 'Joana'; Gender:'f'),
+ ( Lang: 'ro-RO'; VoiceName : 'Ioana'; Gender:'f'),
+ ( Lang: 'ru-RU'; VoiceName : 'Milena'; Gender:'f'),
+ ( Lang: 'sk-SK'; VoiceName : 'Laura'; Gender:'f'),
+ ( Lang: 'sv-SE'; VoiceName : 'Alva'; Gender:'f'),
+ ( Lang: 'th-TH'; VoiceName : 'Kanya'; Gender:'f'),
+ ( Lang: 'tr-TR'; VoiceName : 'Yelda'; Gender:'f'),
+ ( Lang: 'zh-CN'; VoiceName : 'Li-mu'; Gender:'f'),
+ ( Lang: 'zh-CN'; VoiceName : 'Tian-Tian'; Gender:'m'),
+ ( Lang: 'zh-CN'; VoiceName : 'Yu-shu'; Gender:'f'),
+ ( Lang: 'zh-HK'; VoiceName : 'Sin-Ji'; Gender:'f'),
+ ( Lang: 'zh-TW'; VoiceName : 'Mei-Jia'; Gender:'m'),
+ ( Lang: 'en-US'; VoiceName : 'Alex'; Gender:'m') );
+
+function getGenderOfName(const aName:String):Char; // Name --> gender on iOS ( 'm' or 'f')
+var i:integer;
+begin
+ Result := '?'; //unknown
+ for i:=0 to Num_iOS_Voices-1 do
+ if CompareText(iOSVoiceGenders[i].VoiceName,aName)=0 then //found
+ begin
+ Result := iOSVoiceGenders[i].Gender;
+ exit;
+ end;
+end;
+
+function getDeviceCountryCode:String; // 'BR' 'US' .. not used
+const FoundationFwk: string = '/System/Library/Frameworks/Foundation.framework/Foundation';
+var
+ CurrentLocale: NSLocale;
+ CountryISO: NSString;
+begin
+ Result:='??';
+
+ CurrentLocale := TNSLocale.Wrap(TNSLocale.OCClass.currentLocale);
+ CountryISO := TNSString.Wrap(CurrentLocale.objectForKey((CocoaNSStringConst(FoundationFwk, 'NSLocaleCountryCode') as ILocalObject).GetObjectID));
+ Result := UTF8ToString( CountryISO.UTF8String );
+
+ if (Length(Result)>2) then Delete(Result, 3, MaxInt); //trim tail
+end;
+
+function getOSLanguage:String; //default language 'es-ES' 'en-US' 'pt-BR' ..
+var
+ Languages: NSArray;
+begin
+ Languages := TNSLocale.OCClass.preferredLanguages;
+ Result := TNSString.Wrap(Languages.objectAtIndex(0)).UTF8String;
+end;
+
+{ TgoTextToSpeechImplementation }
+
+constructor TgoTextToSpeechImplementation.Create;
+begin
+ inherited;
+ FSpeechSynthesizer := TAVSpeechSynthesizer.Create;
+ FDelegate := TgoTextToSpeechImplementation.TDelegate.Create(Self);
+ FSpeechSynthesizer.setDelegate(FDelegate);
+ Available := True;
+
+ fNativeVoice := nil; // not set yet
+ // alternating mode: One line for the boy, one for the girl
+ fMaleVoice := nil;
+ fFemaleVoice := nil;
+
+ getNativeVoices;
+end;
+
+destructor TgoTextToSpeechImplementation.Destroy;
+begin
+ if (FSpeechSynthesizer <> nil) then
+ FSpeechSynthesizer.release;
+ inherited;
+end;
+
+// Om: mar20:
+Procedure TgoTextToSpeechImplementation.getNativeVoices; // aMaleVoiceLang,aFemaleVoiceLang in format 'pt'
+begin
+ if (NativeSpeechLanguage<>'??-??') then
+ setVoice(NativeSpeechLanguage, NativeSpeechLanguage); //both voices same lang
+end;
+
+// var
+// aLangArray:NSArray;
+// aVoice:AVSpeechSynthesisVoice;
+// i:integer;
+// Slang,Sname:String;
+// begin
+// fNativeVoice := nil;
+// fMaleVoice := nil;
+// fFemaleVoice := nil;
+//
+// aLangArray := TAVSpeechSynthesisVoice.OCClass.speechVoices; //get list of voices
+// for i:=0 to aLangArray.count-1 do
+// begin
+// aVoice := TAVSpeechSynthesisVoice.Wrap( aLangArray.objectAtIndex(i) );
+// Slang := NSStrToStr( aVoice.language );
+// Sname := NSStrToStr( aVoice.name );
+//
+//
+//
+// if (Slang='pt-BR') then
+// begin
+// if ( Copy(Sname,1,7)='Luciana' ) then // '1234567'
+// fFemaleVoice := aVoice; // 'Luciana' casuismos ! :(
+//
+// if ( Copy(Sname,1,6)='Felipe' ) then // '123456'
+// fMaleVoice := aVoice; // 'Felipe'
+// end
+// end;
+//
+// if Assigned(fMaleVoice) then fNativeVoice := fMaleVoice; //any voice will do, but..
+// if Assigned(fFemaleVoice) then fNativeVoice := fFemaleVoice; //.. default = female
+// end;
+
+// Om:
+function TgoTextToSpeechImplementation.getVoices(aList: TStrings): boolean;
+var
+ aLangArray:NSArray;
+ aVoice:AVSpeechSynthesisVoice;
+ i:integer;
+ Slang,Sname,SIdentifier:String;
+
+begin
+ Result := false;
+ aLangArray := TAVSpeechSynthesisVoice.OCClass.speechVoices; //get list of voices
+ for i:=0 to aLangArray.count-1 do
+ begin
+ aVoice := TAVSpeechSynthesisVoice.Wrap( aLangArray.objectAtIndex(i) ); //pode?
+
+ Slang := NSStrToStr( aVoice.language ); // 'pt-BR'
+ Sname := NSStrToStr( aVoice.name ); // 'Fred'
+ SIdentifier := NSStrToStr( aVoice.identifier ); // 'com.apple.ttsbundle_fred-compact'
+
+ aList.Add( IntToStr(i)+' '+Slang );
+ aList.Add( Sname );
+ aList.Add( SIdentifier );
+
+ //if (Slang='pt-PT') then
+ // fNativeVoice := aVoice; //save native voice
+
+ Result := true;
+ end;
+
+ aList.Add('current voice: '+ NSStrToStr( TAVSpeechSynthesisVoice.OCClass.currentLanguageCode ) );
+end;
+
+function TgoTextToSpeechImplementation.getVoiceGender:TVoiceGender; // Om: mar20:
+begin
+ if (fNativeVoice=fFemaleVoice) then Result := vgFemale
+ else if (fNativeVoice=fMaleVoice) then Result := vgMale
+ else Result := vgUnkown;
+end;
+
+function TgoTextToSpeechImplementation.setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; // Om: mar20: set voice w/ spec like 'pt-BR'
+var
+ aLangArray:NSArray;
+ aVoice:AVSpeechSynthesisVoice;
+ i:integer;
+ Slang,Sname,sLangCode,sCountryCode:String;
+ Sex:Char; // 'f' or 'm'
+begin
+ fNativeVoice := nil;
+ fMaleVoice := nil;
+ fFemaleVoice := nil;
+
+ aLangArray := TAVSpeechSynthesisVoice.OCClass.speechVoices; //get list of voices
+ for i:=0 to aLangArray.count-1 do
+ begin
+ aVoice := TAVSpeechSynthesisVoice.Wrap( aLangArray.objectAtIndex(i) );
+ Slang := NSStrToStr( aVoice.language ); // 'pt-BR'
+ Sname := Trim(NSStrToStr( aVoice.name )); // 'Maria'
+
+ //sLangCode := Copy(Slang,1,2); // 'pt'
+ //sCountryCode := Copy(Slang,4,2); // 'BR'
+
+ Sex := getGenderOfName( Sname );
+
+ if (CompareText(sLang,aFemaleVoiceLang)=0) and (Sex='f') then //found female voice language
+ fFemaleVoice := aVoice;
+
+ if (CompareText(sLang,aMaleVoiceLang)=0) and (Sex='m') then //found male voice language
+ fMaleVoice := aVoice;
+
+ // Omar: ad hoc
+ // if ( Copy(Sname,1,7)='Luciana' ) then // '1234567'
+ // fFemaleVoice := aVoice; // 'Luciana' casuismos ! :(
+ //
+ // if ( Copy(Sname,1,6)='Felipe' ) then // '123456'
+ // fMaleVoice := aVoice; // 'Felipe'
+
+ end;
+
+ if Assigned(fMaleVoice) then fNativeVoice := fMaleVoice; //any voice will do, but..
+ if Assigned(fFemaleVoice) then fNativeVoice := fFemaleVoice; //.. default = female
+end;
+
+function TgoTextToSpeechImplementation.IsSpeaking: Boolean;
+begin
+ Result := FSpeechSynthesizer.isSpeaking;
+end;
+
+function TgoTextToSpeechImplementation.Speak(const AText: String): Boolean;
+var
+ Utterance: AVSpeechUtterance;
+ aVoice:AVSpeechSynthesisVoice; //AVSpeechSynthesisVoice;
+
+begin
+ if (AText.Trim = '') then
+ Exit(True);
+
+ if (FSpeechSynthesizer.isSpeaking) then
+ begin
+ { Calling stopSpeakingAtBoundary will also call
+ speechSynthesizerDidCancelSpeechUtterance at some point. We don't want
+ that event to fire here, so we set FFireEvents to False. That flag is
+ set to True again when the next speech is started. }
+ FDelegate.FFireEvents := False;
+ FSpeechSynthesizer.stopSpeakingAtBoundary(AVSpeechBoundaryImmediate);
+ end;
+
+ Utterance := TAVSpeechUtterance.OCClass.speechUtteranceWithString(StrToNSStr(AText));
+
+ // Om: Use saved voice, if any
+ if Assigned(fFemaleVoice) and Assigned(fMaleVoice) then //alternating male-female voices
+ begin
+ if (fNativeVoice=fFemaleVoice) then fNativeVoice:=fMaleVoice
+ else fNativeVoice:=fFemaleVoice;
+ end;
+
+ if Assigned(fNativeVoice) then
+ Utterance.setVoice(fNativeVoice);
+
+ if (not TOSVersion.Check(9)) then
+ Utterance.setRate(DEFAULT_SPEECH_RATE_IOS8_DOWN);
+
+ FSpeechSynthesizer.speakUtterance(Utterance);
+ Result := True;
+end;
+
+procedure TgoTextToSpeechImplementation.Stop;
+begin
+ if (FSpeechSynthesizer.isSpeaking) then
+ { This will also call speechSynthesizerDidCancelSpeechUtterance }
+ FSpeechSynthesizer.stopSpeakingAtBoundary(AVSpeechBoundaryImmediate);
+end;
+
+{ TgoTextToSpeechImplementation.TDelegate }
+
+constructor TgoTextToSpeechImplementation.TDelegate.Create(
+ const ATextToSpeech: TgoTextToSpeechImplementation);
+begin
+ Assert(Assigned(ATextToSpeech));
+ inherited Create;
+ FTextToSpeech := ATextToSpeech;
+end;
+
+procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidCancelSpeechUtterance(
+ synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
+begin
+ if Assigned(FTextToSpeech) and (FFireEvents) then
+ FTextToSpeech.DoSpeechFinished;
+end;
+
+procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidFinishSpeechUtterance(
+ synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
+begin
+ if Assigned(FTextToSpeech) and (FFireEvents) then
+ FTextToSpeech.DoSpeechFinished;
+end;
+
+procedure TgoTextToSpeechImplementation.TDelegate.speechSynthesizerDidStartSpeechUtterance(
+ synthesizer: AVSpeechSynthesizer; utterance: AVSpeechUtterance);
+begin
+ FFireEvents := True;
+ if Assigned(FTextToSpeech) then
+ FTextToSpeech.DoSpeechStarted;
+end;
+
+procedure getNativeVoiceLanguage;
+begin
+ NativeSpeechLanguage := getOSLanguage; // 'pt-BR'
+end;
+
+initialization
+ getNativeVoiceLanguage; // get OS language settings
+end.
diff --git a/TextToSpeech/Grijjy.TextToSpeech.pas b/TextToSpeech/Grijjy.TextToSpeech.pas
index da97618..f8a1806 100644
--- a/TextToSpeech/Grijjy.TextToSpeech.pas
+++ b/TextToSpeech/Grijjy.TextToSpeech.pas
@@ -1,119 +1,133 @@
-unit Grijjy.TextToSpeech;
-{< Universal Text To Speech for iOS, Android, Windows and macOS }
-
-interface
-
-uses
- System.Classes;
-
-type
- { Universal Text To Speech engine.
- Works on iOS, Android, Windows and macOS. Does nothing on other platforms.
- To create an instance, use TgoTextToSpeech.Create. }
- IgoTextToSpeech = interface
- ['{7797ED2A-0695-445A-BA84-495E280F86AB}']
- {$REGION 'Internal Declarations'}
- function _GetAvailable: Boolean;
- function _GetOnAvailable: TNotifyEvent;
- procedure _SetOnAvailable(const AValue: TNotifyEvent);
- function _GetOnSpeechFinished: TNotifyEvent;
- procedure _SetOnSpeechFinished(const AValue: TNotifyEvent);
- function _GetOnSpeechStarted: TNotifyEvent;
- procedure _SetOnSpeechStarted(const AValue: TNotifyEvent);
- {$ENDREGION 'Internal Declarations'}
-
- { Speaks a string of text.
-
- Parameters:
- AText: the text to speak.
-
- Returns:
- True if the engine can speak the text, or False if the text could not
- be spoken for some reason.
-
- If the engine is already speaking some text, then the current speech will
- be terminated (without calling OnSpeechFinished).
-
- This method is asynchronous. It returns immediately and speaks the text
- in the background. IsSpeaking will return False until the engine actually
- starts speaking.
-
- The OnSpeechStarted event is fired when the engine actually starts to
- speak. }
- function Speak(const AText: String): Boolean;
-
- { Stops speaking any current speech. If the engine was speaking, then
- OnSpeechFinished will be fired as well. }
- procedure Stop;
-
- { Whether the engine is currently speaking.
-
- Returns:
- True when the engine has started speaking (and OnSpeechStarted has been
- fired).
- False when the engine has finished speaking (and OnSpeechFinished has
- been fired). }
- function IsSpeaking: Boolean;
-
- { Whether the engine is available and can be used for speaking text (see
- Speak). Always returns True on Windows, macOS and iOS. On Android,
- returns False until the engine has been fully initialized (see also
- OnAvailable) }
- property Available: Boolean read _GetAvailable;
-
- { Is fired when the engine becomes available. Is fired immediately after
- construction on Windows, macOS and iOS. On Android, it is fired once the
- engine has been fully initialized and can be used for speaking.
-
- Is always fired in the main (UI) thread }
- property OnAvailable: TNotifyEvent read _GetOnAvailable write _SetOnAvailable;
-
- { Is fired when the engine starts speaking the text. This may be a little
- while after Speak has been called.
-
- Is always fired in the main (UI) thread }
- property OnSpeechStarted: TNotifyEvent read _GetOnSpeechStarted write _SetOnSpeechStarted;
-
- { Is fired when the engine has finished speaking the text, or when Stop is
- called while the engine was speaking.
-
- Is always fired in the main (UI) thread }
- property OnSpeechFinished: TNotifyEvent read _GetOnSpeechFinished write _SetOnSpeechFinished;
- end;
-
-type
- { Class factory for IgoTextToSpeech. }
- TgoTextToSpeech = class // static
- public
- { Creates a Text To Speech engine. The engine will be initialized with the
- default voice/language for the user's locale.
-
- NOTE: On Android, the speech engine will not be available immediately.
- The Available property will return False until it becomes available. You
- can also set the OnAvailable event to get notified of this. }
- class function Create: IgoTextToSpeech; static;
- end;
-
-implementation
-
-uses
- {$IF Defined(IOS)}
- Grijjy.TextToSpeech.iOS;
- {$ELSEIF Defined(ANDROID)}
- Grijjy.TextToSpeech.Android;
- {$ELSEIF Defined(MSWINDOWS)}
- Grijjy.TextToSpeech.Windows;
- {$ELSEIF Defined(MACOS)}
- Grijjy.TextToSpeech.macOS;
- {$ELSE}
- {$MESSAGE Error 'Text-to-Speech not supported on this platform'}
- {$ENDIF}
-
-{ TgoTextToSpeech }
-
-class function TgoTextToSpeech.Create: IgoTextToSpeech;
-begin
- Result := TgoTextToSpeechImplementation.Create;
-end;
-
-end.
+unit Grijjy.TextToSpeech;
+{< Universal Text To Speech for iOS, Android, Windows and macOS }
+
+// Om: mar20: added fn getVoices - get list of available voices ( only for iOS at this time)
+
+interface
+
+uses
+ System.Classes;
+
+type
+
+ TVoiceGender=(vgMale,vgFemale,vgUnkown); //Om: Kinds of voices
+
+ { Universal Text To Speech engine.
+ Works on iOS, Android, Windows and macOS. Does nothing on other platforms.
+ To create an instance, use TgoTextToSpeech.Create. }
+
+ IgoTextToSpeech = interface
+ ['{7797ED2A-0695-445A-BA84-495E280F86AB}']
+ {$REGION 'Internal Declarations'}
+ function _GetAvailable: Boolean;
+ function _GetOnAvailable: TNotifyEvent;
+ procedure _SetOnAvailable(const AValue: TNotifyEvent);
+ function _GetOnSpeechFinished: TNotifyEvent;
+ procedure _SetOnSpeechFinished(const AValue: TNotifyEvent);
+ function _GetOnSpeechStarted: TNotifyEvent;
+ procedure _SetOnSpeechStarted(const AValue: TNotifyEvent);
+ {$ENDREGION 'Internal Declarations'}
+
+ function getVoices(aList:TStrings):boolean; // Om: mar20: get list of available voices ( only for iOS at this time)
+ function getVoiceGender:TVoiceGender; // Om: mar20:
+ function setVoice(const aMaleVoiceLang,aFemaleVoiceLang:String):boolean; // Om: mar20: set voices w/ 'pt-BR' 'en-US' etc (lang-country)
+
+ { Speaks a string of text.
+
+ Parameters:
+ AText: the text to speak.
+
+ Returns:
+ True if the engine can speak the text, or False if the text could not
+ be spoken for some reason.
+
+ If the engine is already speaking some text, then the current speech will
+ be terminated (without calling OnSpeechFinished).
+
+ This method is asynchronous. It returns immediately and speaks the text
+ in the background. IsSpeaking will return False until the engine actually
+ starts speaking.
+
+ The OnSpeechStarted event is fired when the engine actually starts to
+ speak. }
+ function Speak(const AText: String): Boolean;
+
+ { Stops speaking any current speech. If the engine was speaking, then
+ OnSpeechFinished will be fired as well. }
+ procedure Stop;
+
+ { Whether the engine is currently speaking.
+
+ Returns:
+ True when the engine has started speaking (and OnSpeechStarted has been
+ fired).
+ False when the engine has finished speaking (and OnSpeechFinished has
+ been fired). }
+ function IsSpeaking: Boolean;
+
+ { Whether the engine is available and can be used for speaking text (see
+ Speak). Always returns True on Windows, macOS and iOS. On Android,
+ returns False until the engine has been fully initialized (see also
+ OnAvailable) }
+ property Available: Boolean read _GetAvailable;
+
+ { Is fired when the engine becomes available. Is fired immediately after
+ construction on Windows, macOS and iOS. On Android, it is fired once the
+ engine has been fully initialized and can be used for speaking.
+
+ Is always fired in the main (UI) thread }
+ property OnAvailable: TNotifyEvent read _GetOnAvailable write _SetOnAvailable;
+
+ { Is fired when the engine starts speaking the text. This may be a little
+ while after Speak has been called.
+
+ Is always fired in the main (UI) thread }
+ property OnSpeechStarted: TNotifyEvent read _GetOnSpeechStarted write _SetOnSpeechStarted;
+
+ { Is fired when the engine has finished speaking the text, or when Stop is
+ called while the engine was speaking.
+
+ Is always fired in the main (UI) thread }
+ property OnSpeechFinished: TNotifyEvent read _GetOnSpeechFinished write _SetOnSpeechFinished;
+ end;
+
+type
+ { Class factory for IgoTextToSpeech. }
+ TgoTextToSpeech = class // static
+ public
+ { Creates a Text To Speech engine. The engine will be initialized with the
+ default voice/language for the user's locale.
+
+ NOTE: On Android, the speech engine will not be available immediately.
+ The Available property will return False until it becomes available. You
+ can also set the OnAvailable event to get notified of this. }
+ class function Create: IgoTextToSpeech; static;
+ end;
+
+var
+ NativeSpeechLanguage:String='??-??'; // loaded at unit initialization
+
+implementation
+
+uses
+ {$IF Defined(IOS)}
+ Grijjy.TextToSpeech.iOS;
+ {$ELSEIF Defined(ANDROID)}
+ Grijjy.TextToSpeech.Android;
+ {$ELSEIF Defined(MSWINDOWS)}
+ Grijjy.TextToSpeech.Windows;
+ {$ELSEIF Defined(MACOS)}
+ Grijjy.TextToSpeech.macOS;
+ {$ELSE}
+ {$MESSAGE Error 'Text-to-Speech not supported on this platform'}
+ {$ENDIF}
+
+
+{ TgoTextToSpeech }
+
+class function TgoTextToSpeech.Create: IgoTextToSpeech;
+begin
+ Result := TgoTextToSpeechImplementation.Create;
+end;
+
+end.
diff --git a/TextToSpeech/README.md b/TextToSpeech/README.md
index c8d10ea..3b26ca9 100644
--- a/TextToSpeech/README.md
+++ b/TextToSpeech/README.md
@@ -4,6 +4,31 @@ The code in this directory is a small exercise in designing a cross platform abs
If you are only interested in the end result, then you can stick to the first part of this document and bail when we get to the implementation details.
+# In this fork: by oMAR mar/20
+* Added getVoices ( a list of voices available to Text-to-speech - returns voice descriptions on a TStrings )
+status: Ok for iOS, Android and Windows
+* Loads default voices for the Locale. If available, capture 1 male and 1 female voices for the default OS language
+* Capture one male and one female voices to allow 2 person dialog ( TV journal style )
+* Set voices alternating, one line at a time ( one line for the guy one for the girl )
+ ok for iOS and Android. Not working for Windows.
+ Windows SAPI COM code needs fixing, to do voice selection
+ Hard coded voice selection ( pt-BR ) <-- fix that
+- for iOS there are one male and one female voices available in portuguese-BR
+- for Android, there is a brazilian male voice and a spanish-mexican female that kinda make a funny couple :)
+ on Android, you can download extra voices ( Android Settings )
+
+The example was expanded to:
+
+1) List voices avaivable on the Device. iOS lists all voices available ( 59 in my device). On Android I had to go to device Settings and download the desired voices, if not present. Make sure there are one male and one female voices avalable for your language.
+
+2) Set language with language-country code (p.e. 'en-US', 'pt-BR' ..)
+
+This will capture both female and male voices, used to speak the script.
+
+Om: changes to original JustAddCode code prefixed by "Om:"
+
+check tiktok video: https://www.tiktok.com/@omar_reis/video/6802287150411877638
+
## Choosing a feature set
A common issue with abstracting platform differences is that you must decide on a feature set. A specific feature may be supported on one platform, but not on another. When it comes to text-to-speech, some platforms support choosing a voice, changing the pitch or speech rate, customize pronunciation with markup in the text to speak etc. Other platforms may not support some of these features, or only in an incompatible way.
@@ -422,4 +447,5 @@ begin
end;
```
-We pass the hash map we created before, as well as a `QUEUE_FLUSH` flag that is used to tell the engine to terminate any current speech.
\ No newline at end of file
+We pass the hash map we created before, as well as a `QUEUE_FLUSH` flag that is used to tell the engine to terminate any current speech.
+