macOS Text-To-Speech: what changed
Revision 0 to 1. Removed lines have a minus sign; added lines have a plus sign.
Title: macOS Text-To-Speech
| Old | New | Line | |
|---|---|---|---|
| 1 | + | *(macOS only)* | |
| 2 | + | ||
| 3 | + | ## Overview | |
| 4 | + | ||
| 5 | + | This article covers some ways of generating speech from text strings using the native macOS text-to-speech facilities. This is known as text-to-speech (TTS) or speech synthesis. This synthesized speech is an essential aid to those users with attention or vision disabilities. It is also useful when you want to draw the user’s attention to something important when they might be distracted by something else on the screen. For example, you may want your application to incorporate the capability to speak its dialog box messages to the user. | |
| 6 | + | ||
| 7 | + | There are two distinct methods available to macSO developers: using code to generate speech from text and using utility tools provided with the operating system. | |
| 8 | + | ||
| 9 | + | ### Using code | |
| 10 | + | ||
| 11 | + | There are three options. In their order of evolution, they are: | |
| 12 | + | ||
| 13 | + | - The Speech Synthesis Manager, formerly called the Speech Manager, is a standardized method for macOS applications to generate synthesized speech. The Speech Synthesis Manager is available from macOS 10.0+ (Cheetah) although some of its functions have been deprecated and some new ones were introduced with macOS 10.5 (Leopard). | |
| 14 | + | ||
| 15 | + | - Using an NSSpeechSynthesizer object, you can make your application speak a word, phrase, or sentence to the user. NSSpeechSynthesizer is available from macOS 10.3+ (Panther). The Cocoa interface to speech synthesis in macOS. | |
| 16 | + | ||
| 17 | + | - Using the AVSpeechSynthesizer object that produces synthesized speech from text utterances and provides controls for monitoring or controlling the progress of ongoing speech. Available from macOS 10.14+ (Mojave). | |
| 18 | + | ||
| 19 | + | ### Using utilities | |
| 20 | + | ||
| 21 | + | There are two utility tools provided with macOS that enable you to generate speech from text: | |
| 22 | + | ||
| 23 | + | - The say utility; | |
| 24 | + | - The osacscript utility. | |
| 25 | + | ||
| 26 | + | ## Generating speech from code | |
| 27 | + | ||
| 28 | + | ### Using Speech Synthesis Manager | |
| 29 | + | ||
| 30 | + | The Speech Synthesis Manager, formerly called the Speech Manager, is the part of the macOS that provides a standardized method for applications to generate synthesized speech. While it is the easiest method to use, a number of its functions are now deprecated (see the Apple documentation below for details). | |
| 31 | + | ||
| 32 | + | A simple example using the *SpeakString* function: | |
| 33 | + | ||
| 34 | + | ``` pascal | |
| 35 | + | unit Unit1; | |
| 36 | + | ||
| 37 | + | {$mode objfpc}{$H+} | |
| 38 | + | {$modeswitch objectivec2} | |
| 39 | + | ||
| 40 | + | interface | |
| 41 | + | ||
| 42 | + | uses | |
| 43 | + | Classes, SysUtils, Forms, Dialogs, StdCtrls, MacOSAll; | |
| 44 | + | ||
| 45 | + | type | |
| 46 | + | ||
| 47 | + | { TForm1 } | |
| 48 | + | ||
| 49 | + | TForm1 = Class(TForm) | |
| 50 | + | Button1: TButton; | |
| 51 | + | procedure Button1Click(Sender: TObject); | |
| 52 | + | private | |
| 53 | + | ||
| 54 | + | public | |
| 55 | + | ||
| 56 | + | end; | |
| 57 | + | ||
| 58 | + | var | |
| 59 | + | Form1: TForm1; | |
| 60 | + | ||
| 61 | + | implementation | |
| 62 | + | ||
| 63 | + | {$R *.lfm} | |
| 64 | + | ||
| 65 | + | function mySpeechMgrPresent: OSErr; | |
| 66 | + | var | |
| 67 | + | myErr : OSErr; | |
| 68 | + | myFeature: LongInt = 0; | |
| 69 | + | begin | |
| 70 | + | {Feature being tested} | |
| 71 | + | myerr := Gestalt(gestaltSpeechAttr, myFeature); | |
| 72 | + | ||
| 73 | + | {Test Speech Manager present bit} | |
| 74 | + | if (myErr = noErr) and ((((myFeature) shr (gestaltSpeechMgrPresent)) and 1)=1) then | |
| 75 | + | begin | |
| 76 | + | myErr := SpeakString('The Speech Manager is working and is almost done.'); | |
| 77 | + | ||
| 78 | + | {Wait until synthesizer is done speaking} | |
| 79 | + | while (SpeechBusy <> 0) do | |
| 80 | + | begin | |
| 81 | + | Application.ProcessMessages; | |
| 82 | + | end; | |
| 83 | + | ||
| 84 | + | mySpeechMgrPresent := myErr; | |
| 85 | + | end | |
| 86 | + | else | |
| 87 | + | mySpeechMgrPresent := -1; | |
| 88 | + | end; | |
| 89 | + | ||
| 90 | + | { TForm1 } | |
| 91 | + | ||
| 92 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 93 | + | begin | |
| 94 | + | if(mySpeechMgrPresent = noErr) then | |
| 95 | + | ShowMessage('Speech Manager present: No error') | |
| 96 | + | else | |
| 97 | + | ShowMessage('Speech Manager not present: Error'); | |
| 98 | + | ||
| 99 | + | end; | |
| 100 | + | ||
| 101 | + | end. | |
| 102 | + | ``` | |
| 103 | + | ||
| 104 | + | Unfortunately the *SpeakString* function was deprecated in macOS 10.8 (Mountain Lion) - it is also limited to Pascal strings (ie a string length of 255 characters). The replacement function *SpeakCFString* introduced in macOS 10.5 (Leopard) is a little more complicated, as demonstrated in the reworked example below, and is not limited to 255-character strings: | |
| 105 | + | ||
| 106 | + | ``` pascal | |
| 107 | + | unit Unit1; | |
| 108 | + | ||
| 109 | + | {$mode objfpc}{$H+} | |
| 110 | + | {$modeswitch objectivec2} | |
| 111 | + | ||
| 112 | + | interface | |
| 113 | + | ||
| 114 | + | uses | |
| 115 | + | Classes, SysUtils, Forms, Dialogs, StdCtrls, MacOSAll; | |
| 116 | + | ||
| 117 | + | type | |
| 118 | + | ||
| 119 | + | { TForm1 } | |
| 120 | + | ||
| 121 | + | TForm1 = Class(TForm) | |
| 122 | + | Button1: TButton; | |
| 123 | + | procedure Button1Click(Sender: TObject); | |
| 124 | + | private | |
| 125 | + | ||
| 126 | + | public | |
| 127 | + | ||
| 128 | + | end; | |
| 129 | + | ||
| 130 | + | var | |
| 131 | + | Form1: TForm1; | |
| 132 | + | ||
| 133 | + | implementation | |
| 134 | + | ||
| 135 | + | {$R *.lfm} | |
| 136 | + | ||
| 137 | + | function MySpeechMgrPresent: OSErr; | |
| 138 | + | var | |
| 139 | + | myErr : OSErr; | |
| 140 | + | myFeature: LongInt = 0; | |
| 141 | + | mySpChan : SpeechChannelRecordPtr = Nil; | |
| 142 | + | begin | |
| 143 | + | {Feature being tested} | |
| 144 | + | myerr := Gestalt(gestaltSpeechAttr, myFeature); | |
| 145 | + | ||
| 146 | + | {Test Speech Manager present bit} | |
| 147 | + | if (myErr = noErr) and ((((myFeature) shr (gestaltSpeechMgrPresent)) and 1)=1) then | |
| 148 | + | begin | |
| 149 | + | myErr := NewSpeechChannel(Nil, mySpChan); | |
| 150 | + | myErr := SpeakCFString(mySpChan, CFStr('The Speech Manager is working and is almost done.'), Nil); | |
| 151 | + | ||
| 152 | + | {Wait until synthesizer is done speaking} | |
| 153 | + | while (SpeechBusy <> 0) do | |
| 154 | + | begin | |
| 155 | + | Application.ProcessMessages; | |
| 156 | + | end; | |
| 157 | + | ||
| 158 | + | mySpeechMgrPresent := myErr; | |
| 159 | + | end | |
| 160 | + | else | |
| 161 | + | mySpeechMgrPresent := -1; | |
| 162 | + | end; | |
| 163 | + | ||
| 164 | + | { TForm1 } | |
| 165 | + | ||
| 166 | + | procedure TForm1.Button1Click(Sender: TObject); | |
| 167 | + | begin | |
| 168 | + | if(mySpeechMgrPresent = noErr) then | |
| 169 | + | ShowMessage('Speech Manager present: No error') | |
| 170 | + | else | |
| 171 | + | ShowMessage('Speech Manager not present: Error'); | |
| 172 | + | end; | |
| 173 | + | ||
| 174 | + | end. | |
| 175 | + | ``` | |
| 176 | + | ||
| 177 | + | See the Apple documentation below for more details of the Speech Synthesis Manager's functionality. | |
| 178 | + | ||
| 179 | + | ### Using NSSpeechSynthesizer | |
| 180 | + | ||
| 181 | + | Using an NSSpeechSynthesizer object to “pronounce” text is easy. You initialize the object with a voice and send a startSpeakingString: message to it, passing in an NSString object representing the text to speak. | |
| 182 | + | ||
| 183 | + | Source: [forum post by kogs](http://forum.lazarus.freepascal.org/index.php/topic,23890.msg143222.html#msg143222) and contributions by skalogryz (updated to compile with Lazarus 2.1 and FPC 3.3.1 by Trev): | |
| 184 | + | ||
| 185 | + | The code for the speech synthesis object, speechsynthesizer.pas: | |
| 186 | + | ||
| 187 | + | ``` pascal | |
| 188 | + | { Parsed from Appkit.framework NSSpeechSynthesizerSpeechSynthesize.h } | |
| 189 | + | unit speechsynthesizer; | |
| 190 | + | ||
| 191 | + | {$mode objfpc}{$H+} | |
| 192 | + | {$modeswitch objectivec1} | |
| 193 | + | ||
| 194 | + | interface | |
| 195 | + | ||
| 196 | + | {$linkframework AppKit} | |
| 197 | + | ||
| 198 | + | uses | |
| 199 | + | Classes, MacOSAll, CocoaUtils; //, CarbonProc; Omit for Cocoa compilation | |
| 200 | + | ||
| 201 | + | type | |
| 202 | + | ||
| 203 | + | { NSSpeechSynthesizer } | |
| 204 | + | ||
| 205 | + | NSSpeechSynthesizer = objcclass external (NSObject) | |
| 206 | + | private | |
| 207 | + | _privateNSSpeechSynthesizerVars: id; | |
| 208 | + | // function and procedure | |
| 209 | + | // init with voice | |
| 210 | + | function initWithVoice(voice_: NSString): id; message 'initWithVoice:'; | |
| 211 | + | // Speaking | |
| 212 | + | function startSpeakingString(string_: NSString): Boolean; message 'startSpeakingString:'; | |
| 213 | + | // function startSpeakingString_toURL(string_: NSString; url: NSURL): Boolean; message 'startSpeakingString:toURL:'; | |
| 214 | + | function isSpeaking: Boolean; message 'isSpeaking'; | |
| 215 | + | procedure stopSpeaking; message 'stopSpeaking'; | |
| 216 | + | // procedure stopSpeakingAtBoundary(boundary: NSSpeechBoundary); message 'stopSpeakingAtBoundary:'; | |
| 217 | + | // procedure pauseSpeakingAtBoundary(boundary: NSSpeechBoundary); message 'pauseSpeakingAtBoundary:'; | |
| 218 | + | procedure continueSpeaking; message 'continueSpeaking'; | |
| 219 | + | function delegate: NSObject; message 'delegate'; | |
| 220 | + | procedure setDelegate(anObject: NSObject); message 'setDelegate:'; | |
| 221 | + | // voice | |
| 222 | + | function voice: NSString; message 'voice'; | |
| 223 | + | function setVoice(voice_: NSString): Boolean; message 'setVoice:'; | |
| 224 | + | // rate | |
| 225 | + | function rate: single; message 'rate'; | |
| 226 | + | procedure setRate(rate_: single); message 'setRate:'; | |
| 227 | + | // volume | |
| 228 | + | function volume: single; message 'volume'; | |
| 229 | + | procedure setVolume(volume_: single); message 'setVolume:'; | |
| 230 | + | // function usesFeedbackWindow: Boolean; message 'usesFeedbackWindow'; | |
| 231 | + | // procedure setUsesFeedbackWindow(flag: Boolean); message 'setUsesFeedbackWindow:'; | |
| 232 | + | // procedure addSpeechDictionary(speechDictionary: NSDictionary); message 'addSpeechDictionary:'; | |
| 233 | + | // phonemes from text | |
| 234 | + | function phonemesFromText(text: NSString): NSString; message 'phonemesFromText:'; | |
| 235 | + | // function objectForProperty_error(property_: NSString; outError: NSErrorPointer): id; message 'objectForProperty:error:'; | |
| 236 | + | // function setObject_forProperty_error(object_: id; property_: NSString; outError: NSErrorPointer): Boolean; message 'setObject:forProperty:error:'; | |
| 237 | + | // class function isAnyApplicationSpeaking: Boolean; message 'isAnyApplicationSpeaking'; | |
| 238 | + | // class function defaultVoice: NSString; message 'defaultVoice'; | |
| 239 | + | // class function availableVoices: NSArray; message 'availableVoices'; | |
| 240 | + | // class function attributesForVoice(voice_: NSString): NSDictionary; message 'attributesForVoice:'; | |
| 241 | + | end; | |
| 242 | + | ||
| 243 | + | TSpeechDelegate = objcclass; | |
| 244 | + | ||
| 245 | + | { TSpeechSynthesize } | |
| 246 | + | ||
| 247 | + | TSpeechSynthesizer = class | |
| 248 | + | private | |
| 249 | + | fOnFinish : TNotifyEvent; | |
| 250 | + | // Speech are the Synthesizer and the Delegate | |
| 251 | + | SS: NSSpeechSynthesizer; | |
| 252 | + | Del: TSpeechDelegate; | |
| 253 | + | function NSStr(const stringA: String): NSString; | |
| 254 | + | // Voice | |
| 255 | + | function GetVoice: String; | |
| 256 | + | procedure SetVoice(stringVoice: String); | |
| 257 | + | // Rate | |
| 258 | + | function GetRate: Integer; | |
| 259 | + | procedure SetRate(integerRate: Integer); | |
| 260 | + | // Volume | |
| 261 | + | function GetVolume: Integer; | |
| 262 | + | procedure SetVolume(integerVolume: Integer); | |
| 263 | + | // AllocDelegate | |
| 264 | + | procedure AllocDelegate; | |
| 265 | + | protected | |
| 266 | + | procedure DoFinishedSpeaking; | |
| 267 | + | public | |
| 268 | + | constructor Create; | |
| 269 | + | constructor Create(stringVoice: String); | |
| 270 | + | destructor Destroy; override; | |
| 271 | + | // Speaking | |
| 272 | + | function StartSpeakingString(stringA: String): Boolean; | |
| 273 | + | procedure StopSpeaking; | |
| 274 | + | function IsSpeaking: Boolean; // speaking is yes/no | |
| 275 | + | procedure ContinueSpeaking; | |
| 276 | + | // Phonemes from text | |
| 277 | + | function PhonemesFromText(stringText: String): String; | |
| 278 | + | // Voice | |
| 279 | + | property Voice: String read GetVoice write SetVoice; | |
| 280 | + | // Rate | |
| 281 | + | property Rate: Integer read GetRate write SetRate; | |
| 282 | + | // Volume | |
| 283 | + | property Volume: Integer read GetVolume write SetVolume; | |
| 284 | + | // Notification on end of speech | |
| 285 | + | property OnFinish: TNotifyEvent read fOnFinish write fOnFinish; | |
| 286 | + | end; | |
| 287 | + | ||
| 288 | + | { TSpeechDelegate } | |
| 289 | + | ||
| 290 | + | TSpeechDelegate = objcclass(NSObject) | |
| 291 | + | public | |
| 292 | + | Obj : TSpeechSynthesizer; | |
| 293 | + | procedure SpeechSynthesizer_DidFinishSpeaking(sender: NSSpeechSynthesizer; | |
| 294 | + | finishedSpeakingSuccess: Boolean); message | |
| 295 | + | 'speechSynthesizer:didFinishSpeaking:'; | |
| 296 | + | {procedure speechSynthesizer_willSpeakWord_ofString(sender: NSSpeechSynthesizer; characterRange: NSRange; string_: NSString); message 'speechSynthesizer:willSpeakWord:ofString:'; | |
| 297 | + | procedure speechSynthesizer_willSpeakPhoneme(sender: NSSpeechSynthesizer; phonemeOpcode: cshort); message 'speechSynthesizer:willSpeakPhoneme:'; | |
| 298 | + | procedure speechSynthesizer_didEncounterErrorAtIndex_ofString_message(sender: NSSpeechSynthesizer; characterIndex: NSUInteger; string_: NSString; message: NSString); message 'speechSynthesizer:didEncounterErrorAtIndex:ofString:message:'; | |
| 299 | + | procedure speechSynthesizer_didEncounterSyncMessage(sender: NSSpeechSynthesizer; message: NSString); message 'speechSynthesizer:didEncounterSyncMessage:';} | |
| 300 | + | end; | |
| 301 | + | ||
| 302 | + | function NSStringToString(ns: NSString): String; | |
| 303 | + | ||
| 304 | + | implementation | |
| 305 | + | ||
| 306 | + | {------------------------------------------------------------------------------- | |
| 307 | + | function NSStringToString | |
| 308 | + | -------------------------------------------------------------------------------} | |
| 309 | + | function NSStringToString(ns: NSString): String; | |
| 310 | + | begin | |
| 311 | + | Result := CFStringToStr(CFStringRef(ns)); | |
| 312 | + | end; | |
| 313 | + | ||
| 314 | + | //------------------------------------------------------------------------------ | |
| 315 | + | ||
| 316 | + | { TSpeechDelegate } | |
| 317 | + | ||
| 318 | + | {------------------------------------------------------------------------------- | |
| 319 | + | procedure SpeechSynthesizer_DidFinishSpeaking | |
| 320 | + | -------------------------------------------------------------------------------} | |
| 321 | + | procedure TSpeechDelegate.SpeechSynthesizer_DidFinishSpeaking( | |
| 322 | + | sender: NSSpeechSynthesizer; finishedSpeakingSuccess: Boolean); | |
| 323 | + | begin | |
| 324 | + | if Assigned(Obj) then Obj.DoFinishedSpeaking; | |
| 325 | + | end; | |
| 326 | + | ||
| 327 | + | //------------------------------------------------------------------------------ | |
| 328 | + | ||
| 329 | + | { TSpeechSynthesize } | |
| 330 | + | ||
| 331 | + | {------------------------------------------------------------------------------- | |
| 332 | + | function NSStr | |
| 333 | + | -------------------------------------------------------------------------------} | |
| 334 | + | function TSpeechSynthesizer.NSStr(const stringA: String): NSString; | |
| 335 | + | begin | |
| 336 | + | // converting string to NSString (CFStringRef and NSString are interchangable) | |
| 337 | + | Result := NSString(CFStr(PChar(stringA))); | |
| 338 | + | end; | |
| 339 | + | ||
| 340 | + | {------------------------------------------------------------------------------- | |
| 341 | + | function StartSpeakingString | |
| 342 | + | -------------------------------------------------------------------------------} | |
| 343 | + | function TSpeechSynthesizer.StartSpeakingString(stringA: String): Boolean; | |
| 344 | + | begin | |
| 345 | + | Result := SS.startSpeakingString(NSStr(stringA)); | |
| 346 | + | end; | |
| 347 | + | ||
| 348 | + | {------------------------------------------------------------------------------- | |
| 349 | + | procedure StopSpeaking | |
| 350 | + | -------------------------------------------------------------------------------} | |
| 351 | + | procedure TSpeechSynthesizer.StopSpeaking; | |
| 352 | + | begin | |
| 353 | + | SS.stopSpeaking; | |
| 354 | + | end; | |
| 355 | + | ||
| 356 | + | {------------------------------------------------------------------------------- | |
| 357 | + | function IsSpeaking | |
| 358 | + | -------------------------------------------------------------------------------} | |
| 359 | + | function TSpeechSynthesizer.IsSpeaking: Boolean; | |
| 360 | + | begin | |
| 361 | + | Result := False; | |
| 362 | + | Result := SS.isSpeaking; | |
| 363 | + | end; | |
| 364 | + | ||
| 365 | + | {------------------------------------------------------------------------------- | |
| 366 | + | procedure ContinueSpeakinG | |
| 367 | + | -------------------------------------------------------------------------------} | |
| 368 | + | procedure TSpeechSynthesizer.ContinueSpeaking; | |
| 369 | + | begin | |
| 370 | + | SS.continueSpeaking; | |
| 371 | + | end; | |
| 372 | + | ||
| 373 | + | {------------------------------------------------------------------------------- | |
| 374 | + | function GetVoice | |
| 375 | + | -------------------------------------------------------------------------------} | |
| 376 | + | function TSpeechSynthesizer.GetVoice: String; | |
| 377 | + | begin | |
| 378 | + | Result := NSStringToString(SS.voice); | |
| 379 | + | end; | |
| 380 | + | ||
| 381 | + | {------------------------------------------------------------------------------- | |
| 382 | + | procedure SetVoice | |
| 383 | + | -------------------------------------------------------------------------------} | |
| 384 | + | procedure TSpeechSynthesizer.SetVoice(stringVoice: String); | |
| 385 | + | begin | |
| 386 | + | SS.setVoice(NSStr(stringVoice)); | |
| 387 | + | end; | |
| 388 | + | ||
| 389 | + | {------------------------------------------------------------------------------- | |
| 390 | + | function GetRate | |
| 391 | + | -------------------------------------------------------------------------------} | |
| 392 | + | function TSpeechSynthesizer.GetRate: Integer; | |
| 393 | + | begin | |
| 394 | + | Result := Integer(SS.rate); | |
| 395 | + | end; | |
| 396 | + | ||
| 397 | + | {------------------------------------------------------------------------------- | |
| 398 | + | procedure SetRate | |
| 399 | + | -------------------------------------------------------------------------------} | |
| 400 | + | procedure TSpeechSynthesizer.SetRate(integerRate: Integer); | |
| 401 | + | begin | |
| 402 | + | SS.setRate(single(integerRate)); | |
| 403 | + | end; | |
| 404 | + | ||
| 405 | + | {------------------------------------------------------------------------------- | |
| 406 | + | function GetVolume | |
| 407 | + | -------------------------------------------------------------------------------} | |
| 408 | + | function TSpeechSynthesizer.GetVolume: Integer; | |
| 409 | + | begin | |
| 410 | + | Result := Integer(SS.rate); | |
| 411 | + | end; | |
| 412 | + | ||
| 413 | + | {------------------------------------------------------------------------------- | |
| 414 | + | procedure SetRate | |
| 415 | + | -------------------------------------------------------------------------------} | |
| 416 | + | procedure TSpeechSynthesizer.SetVolume(integerVolume: Integer); | |
| 417 | + | begin | |
| 418 | + | SS.setRate(Integer(integerVolume)); | |
| 419 | + | end; | |
| 420 | + | ||
| 421 | + | {------------------------------------------------------------------------------- | |
| 422 | + | function PhonemesFromText | |
| 423 | + | -------------------------------------------------------------------------------} | |
| 424 | + | function TSpeechSynthesizer.PhonemesFromText(stringText: String): String; | |
| 425 | + | begin | |
| 426 | + | Result := NSStringToString(SS.phonemesFromText(NSStr(stringText))); | |
| 427 | + | end; | |
| 428 | + | ||
| 429 | + | {------------------------------------------------------------------------------- | |
| 430 | + | procedure AllocDelegate | |
| 431 | + | -------------------------------------------------------------------------------} | |
| 432 | + | procedure TSpeechSynthesizer.AllocDelegate; | |
| 433 | + | begin | |
| 434 | + | Del := TSpeechDelegate.alloc.init; | |
| 435 | + | Del.Obj:=Self; | |
| 436 | + | SS.setDelegate(Del); | |
| 437 | + | end; | |
| 438 | + | ||
| 439 | + | {------------------------------------------------------------------------------- | |
| 440 | + | procedure SetRate | |
| 441 | + | -------------------------------------------------------------------------------} | |
| 442 | + | procedure TSpeechSynthesizer.DoFinishedSpeaking; | |
| 443 | + | begin | |
| 444 | + | if Assigned(fOnFinish) then fOnFinish(Self); | |
| 445 | + | end; | |
| 446 | + | ||
| 447 | + | {------------------------------------------------------------------------------- | |
| 448 | + | constructor Create | |
| 449 | + | -------------------------------------------------------------------------------} | |
| 450 | + | constructor TSpeechSynthesizer.Create; | |
| 451 | + | begin | |
| 452 | + | inherited; | |
| 453 | + | SS := NSSpeechSynthesizer.alloc.init; | |
| 454 | + | AllocDelegate; | |
| 455 | + | end; | |
| 456 | + | ||
| 457 | + | constructor TSpeechSynthesizer.Create(stringVoice: String); | |
| 458 | + | begin | |
| 459 | + | SS := NSSpeechSynthesizer.alloc.initWithVoice(NSStr(stringVoice)); | |
| 460 | + | AllocDelegate; | |
| 461 | + | end; | |
| 462 | + | ||
| 463 | + | destructor TSpeechSynthesizer.Destroy; | |
| 464 | + | begin | |
| 465 | + | Del.release; | |
| 466 | + | SS.release; | |
| 467 | + | inherited Destroy; | |
| 468 | + | end; | |
| 469 | + | ||
| 470 | + | end. | |
| 471 | + | ``` | |
| 472 | + | ||
| 473 | + | Example code: | |
| 474 | + | ||
| 475 | + | ``` pascal | |
| 476 | + | unit Unit1; | |
| 477 | + | ||
| 478 | + | {$mode objfpc}{$H+} | |
| 479 | + | ||
| 480 | + | interface | |
| 481 | + | ||
| 482 | + | uses | |
| 483 | + | Classes, Forms, StdCtrls, Menus, speechsynthesize; | |
| 484 | + | ||
| 485 | + | type | |
| 486 | + | ||
| 487 | + | { TForm1 } | |
| 488 | + | ||
| 489 | + | TForm1 = class(TForm) | |
| 490 | + | MainMenu1: TMainMenu; | |
| 491 | + | Memo1: TMemo; | |
| 492 | + | MenuItemEdit: TMenuItem; | |
| 493 | + | MenuItemEditSpeech: TMenuItem; | |
| 494 | + | MenuItemEditSpeechStart: TMenuItem; | |
| 495 | + | MenuItemEditSpeechStop: TMenuItem; | |
| 496 | + | procedure FormCreate(Sender: TObject); | |
| 497 | + | procedure FormDestroy(Sender: TObject); | |
| 498 | + | procedure MenuItemEditSpeechStartClick(Sender: TObject); | |
| 499 | + | procedure MenuItemEditSpeechStopClick(Sender: TObject); | |
| 500 | + | private | |
| 501 | + | { private declarations } | |
| 502 | + | SpeechSynthesizer: TSpeechSynthesizer; | |
| 503 | + | public | |
| 504 | + | { public declarations } | |
| 505 | + | procedure OnFinish(Sender: TObject); | |
| 506 | + | end; | |
| 507 | + | ||
| 508 | + | var | |
| 509 | + | Form1: TForm1; | |
| 510 | + | ||
| 511 | + | implementation | |
| 512 | + | ||
| 513 | + | {$R *.lfm} | |
| 514 | + | ||
| 515 | + | { TForm1 } | |
| 516 | + | ||
| 517 | + | procedure TForm1.FormCreate(Sender: TObject); | |
| 518 | + | begin | |
| 519 | + | SpeechSynthesizer := TSpeechSynthesizer.Create; | |
| 520 | + | ||
| 521 | + | // SpeechSynthesizer.OnFinish <-@OnFinish (Self) | |
| 522 | + | SpeechSynthesizer.OnFinish := @OnFinish; | |
| 523 | + | ||
| 524 | + | MenuItemEditSpeechStop.Enabled := False; | |
| 525 | + | end; | |
| 526 | + | ||
| 527 | + | procedure TForm1.FormDestroy(Sender: TObject); | |
| 528 | + | begin | |
| 529 | + | SpeechSynthesizer.Free; | |
| 530 | + | end; | |
| 531 | + | ||
| 532 | + | procedure TForm1.MenuItemEditSpeechStartClick(Sender: TObject); | |
| 533 | + | begin | |
| 534 | + | // IsSpeaking - yes -> StopSpeaking | |
| 535 | + | if SpeechSynthesizer.IsSpeaking then | |
| 536 | + | begin | |
| 537 | + | SpeechSynthesizer.StopSpeaking; | |
| 538 | + | MenuItemEditSpeechStop.Enabled := False; | |
| 539 | + | end; | |
| 540 | + | ||
| 541 | + | // StartSpeakingString | |
| 542 | + | SpeechSynthesizer.StartSpeakingString(Memo1.Text); | |
| 543 | + | ||
| 544 | + | MenuItemEditSpeechStop.Enabled := True; | |
| 545 | + | end; | |
| 546 | + | ||
| 547 | + | procedure TForm1.MenuItemEditSpeechStopClick(Sender: TObject); | |
| 548 | + | begin | |
| 549 | + | SpeechSynthesizer.StopSpeaking; | |
| 550 | + | end; | |
| 551 | + | ||
| 552 | + | { OnFinish } | |
| 553 | + | ||
| 554 | + | procedure TForm1.OnFinish(Sender: TObject); | |
| 555 | + | begin | |
| 556 | + | // not SpeechSynthesizer.IsSpeaking - yes or no | |
| 557 | + | if not SpeechSynthesizer.IsSpeaking then | |
| 558 | + | begin | |
| 559 | + | MenuItemEditSpeechStart.Enabled := True; | |
| 560 | + | MenuItemEditSpeechStop.Enabled := False; | |
| 561 | + | end; | |
| 562 | + | end; | |
| 563 | + | ||
| 564 | + | end. | |
| 565 | + | ``` | |
| 566 | + | ||
| 567 | + | For more details of the NSSpeechSynthesizer object's functionality, see the Apple documentation below. | |
| 568 | + | ||
| 569 | + | ### Using AVSpeechSynthesis | |
| 570 | + | ||
| 571 | + | The AVSpeechSynthesizer class produces synthesized speech from text and provides methods for controlling or monitoring the progress of ongoing speech. To speak some text, you must first create an AVSpeechUtterance instance containing the text. (Optionally, you may also use the utterance object to control parameters affecting its speech, such as voice, pitch, and rate.) Then, pass it to the speakUtterance method on a speech synthesizer instance to speak that utterance. By default, AVSpeechSynthesizer will speak using a voice based on the user’s current language preferences. | |
| 572 | + | ||
| 573 | + | The speech synthesizer maintains a queue of utterances to be spoken. If the synthesizer is not currently speaking, calling speakUtterance: begins speaking that utterance immediately (or begin waiting through its preUtteranceDelay if one is set). If the synthesizer is speaking, utterances are added to a queue and spoken in the order they are received. | |
| 574 | + | ||
| 575 | + | After speech has begun, you can use the synthesizer object to pause or stop speech. After speech is paused, it may be continued from the point at which it left off; stopping ends speech entirely, removing any utterances yet to be spoken from the synthesizer’s queue. You may monitor the speech synthesizer by examining its speaking and paused properties, or by setting a delegate. Messages in the AVSpeechSynthesizerDelegate protocol are sent as significant events occur during speech synthesis. | |
| 576 | + | ||
| 577 | + | ## Generating speech using utilities | |
| 578 | + | ||
| 579 | + | ### Using /usr/bin/say | |
| 580 | + | ||
| 581 | + | This tool uses the Speech Synthesis manager to convert input text to audible speech and either play it through the sound output device chosen in System Preferences or save it to an AIFF file. | |
| 582 | + | ||
| 583 | + | ``` pascal | |
| 584 | + | ... | |
| 585 | + | ||
| 586 | + | Uses | |
| 587 | + | Unix, | |
| 588 | + | BaseUnix, | |
| 589 | + | ...; | |
| 590 | + | ||
| 591 | + | ... | |
| 592 | + | ||
| 593 | + | procedure TForm1.MenuItem22Click(Sender: TObject); | |
| 594 | + | var | |
| 595 | + | status: LongInt; | |
| 596 | + | tts: String; | |
| 597 | + | begin | |
| 598 | + | tts := 'Hello, this is a test.'; | |
| 599 | + | status := fpSystem('/usr/bin/say "' + tts + '"'); | |
| 600 | + | ShowMessage('Exit status: ' + IntToStr(wexitStatus(status))); | |
| 601 | + | end; | |
| 602 | + | ``` | |
| 603 | + | ||
| 604 | + | ``` pascal | |
| 605 | + | Program sayhello; | |
| 606 | + | ||
| 607 | + | Uses | |
| 608 | + | Process; | |
| 609 | + | ||
| 610 | + | Var | |
| 611 | + | AProcess:TProcess; | |
| 612 | + | ||
| 613 | + | Begin | |
| 614 | + | AProcess := TProcess.Create(nil); | |
| 615 | + | Try | |
| 616 | + | AProcess.Executable := '/usr/bin/say'; | |
| 617 | + | AProcess.Parameters.Add( '-o'); | |
| 618 | + | AProcess.Parameters.Add( '/Users/<you>/Desktop/hello.aiff'); // save speech to file on your desktop | |
| 619 | + | AProcess.Parameters.Add( 'hello'); | |
| 620 | + | AProcess.Options := AProcess.Options + [poWaitOnExit]; | |
| 621 | + | AProcess.Execute; | |
| 622 | + | Finally | |
| 623 | + | AProcess.Free; | |
| 624 | + | End; | |
| 625 | + | End. | |
| 626 | + | ``` | |
| 627 | + | ||
| 628 | + | For more details on how to use this command, open a Terminal and type `man say` for the manual page. | |
| 629 | + | ||
| 630 | + | ### Using osascript | |
| 631 | + | ||
| 632 | + | The `osascript` command line utility executes the given OSA script, which may be plain text or a compiled script (.scpt) created by the Applications \> Utilities \> Script Editor application or the `osacompile` command line utility. By default, `osascript` treats plain text as AppleScript, but you can change this using the -l option to JavaScript or the Generic Scripting System. To get a list of the OSA languages installed on your system, run the `osalang` command line utility. | |
| 633 | + | ||
| 634 | + | Example: | |
| 635 | + | ||
| 636 | + | ``` pascal | |
| 637 | + | // | |
| 638 | + | // From a forum post by jwdietrich: | |
| 639 | + | // | |
| 640 | + | ||
| 641 | + | var | |
| 642 | + | s: longint; | |
| 643 | + | t: String; | |
| 644 | + | begin | |
| 645 | + | t := 'This is a test.'; | |
| 646 | + | s := fpSystem('osascript -e ''say "' + t + '"'''); | |
| 647 | + | end; | |
| 648 | + | ``` | |
| 649 | + | ||
| 650 | + | For more details on how to use this command, open a Terminal and type `man osascript` for the manual page. | |
| 651 | + | ||
| 652 | + | ## See also | |
| 653 | + | ||
| 654 | + | - [[LCL Accessibility|Using the macOS screenreader]] | |
| 655 | + | - [[Speech Synthesis]] Details of cross-platform and native operating system solutions. | |
| 656 | + | ||
| 657 | + | ## External links | |
| 658 | + | ||
| 659 | + | - [Apple: Speech Synthesis](https://developer.apple.com/documentation/avfoundation/speech_synthesis). | |
| 660 | + | - [Apple: Speech Manager/Speech Synthesis Manager](https://developer.apple.com/library/archive/documentation/mac/pdf/Sound/Speech_Manager.pdf) (PDF) Legacy Pascal code; macOS 10.0+ (with deprecations). | |
| 661 | + | - [Apple: NSSpeechSynthesizer](https://developer.apple.com/documentation/appkit/nsspeechsynthesizer) macOS 10.3+. | |
| 662 | + | - [Apple: AVSpeechSynthesizer](https://developer.apple.com/documentation/avfoundation/avspeechsynthesizer) macOS 10.14+. | |
| 663 | + | ||
| 664 | + | --- | |
| 665 | + | *From the Free Pascal wiki, page "macOS Text-To-Speech", as it stood on 2020-06-04 after a change by Trev, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's [[Licensing]] page. If you have concerns about this material, [message the admin](/wiki-contact).* |
