PageRenderTime 49ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/tags/v2-59/mh/lib/Voice_Text.pm

#
Perl | 514 lines | 482 code | 17 blank | 15 comment | 36 complexity | dfb262c5163f6a48ff4cf13f05ba1936 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.0, GPL-3.0
  1. package Voice_Text;
  2. use strict;
  3. my ($VTxt, $VTxt_version, $VTxt_festival, $speak_pgm, $save_mute_esd, $save_change_volume, %pronouncable);
  4. my ($ViaVoiceTTS); #mod for ViaVoiceTTS
  5. my $is_speaking_timer = new Timer;
  6. sub init {
  7. if ($main::config_parms{voice_text} =~ /festival/i) {
  8. print "Creating festival TTS socket\n";
  9. my $festival_address = "$main::config_parms{festival_host}:$main::config_parms{festival_port}";
  10. $VTxt_festival = new Socket_Item(undef, undef, $festival_address);
  11. start $VTxt_festival;
  12. if ($main::config_parms{festival_init_cmds}) {
  13. print "Data sent to festival: $main::config_parms{festival_init_cmds}\n";
  14. set $VTxt_festival qq[$main::config_parms{festival_init_cmds}];
  15. }
  16. }
  17. if ($main::config_parms{voice_text} =~ /vv_tts/i) {
  18. $speak_pgm = qq[$main::Pgm_Path/vv_tts.pl];
  19. $speak_pgm .= " -prescript " . $main::config_parms{vv_tts_prescript} if $main::config_parms{vv_tts_prescript};
  20. $speak_pgm .= " -postscript " . $main::config_parms{vv_tts_postscript} if $main::config_parms{vv_tts_postscript};
  21. $speak_pgm .= " -playcmd " . $main::config_parms{vv_tts_playcmd} if $main::config_parms{vv_tts_playcmd};
  22. $speak_pgm .= " -default_sound " . $main::config_parms{vv_tts_default_sound} if $main::config_parms{vv_tts_default_sound};
  23. print "VV TTS command string: $speak_pgm\n";
  24. }
  25. if ($main::config_parms{voice_text} =~ /program (\S+)/i) {
  26. $speak_pgm = $1;
  27. $speak_pgm .= " " . $main::config_parms{speak_volume} if $main::config_parms{speak_volume};
  28. $speak_pgm .= " " . $main::config_parms{speak_pitch} if $main::config_parms{speak_pitch};
  29. $speak_pgm .= " " . $main::config_parms{speak_rate} if $main::config_parms{speak_rate};
  30. $speak_pgm .= " " . $main::config_parms{speak_voice} if $main::config_parms{speak_voice};
  31. print "Speak string: $speak_pgm\n";
  32. }
  33. if ($main::config_parms{voice_text} =~ /viavoice/i) {
  34. $ViaVoiceTTS = 1; #define $ViaVoiceTTS if 'voice_text=viavoice'
  35. print "Using ViaVoiceTTS.pm for speech.\n";
  36. }
  37. if ($main::config_parms{voice_text} =~ /ms/i and $main::OS_win) {
  38. print "Creating MS TTS object for voice_text=$main::config_parms{voice_text} ...";
  39. # Test and default to the new SDK 5 SAPI
  40. $VTxt_version = lc $main::config_parms{voice_text};
  41. unless ($VTxt_version eq 'msv4') {
  42. if ($VTxt = Win32::OLE->new('Sapi.SpVoice')) {
  43. $VTxt_version = 'msv5';
  44. }
  45. else {
  46. $VTxt_version = 'msv4';
  47. }
  48. }
  49. if ($VTxt_version eq 'msv4') {
  50. $VTxt = Win32::OLE->new('Speech.VoiceText');
  51. unless ($VTxt) {
  52. print "\n\nError, could not create ms Speech TTS object. ", Win32::OLE->LastError(), "\n\n";
  53. return;
  54. }
  55. # print "Registering the MS TTS object\n";
  56. $VTxt->Register("Local PC", "perl voice_text.pm");
  57. # $VTxt->{Enabled} = 1;
  58. }
  59. print " engine used: $VTxt_version\n";
  60. }
  61. return $VTxt;
  62. }
  63. sub speak_text {
  64. my(%parms) = @_;
  65. # Allow for pause,resume,stop,ff,rew. Also allow mode to set rate
  66. if (my $mode = $parms{mode}) {
  67. if ($mode eq 'fast' or $mode eq 'normal' or $mode eq 'slow' or $mode =~ /^[\+\-]?\d+$/) {
  68. $parms{rate} = $mode;
  69. }
  70. else {
  71. &set_mode($mode);
  72. }
  73. }
  74. $parms{text} = &set_rate($parms{rate}, $parms{text}) if $parms{rate}; # Allow for slow,normal,fast,wpm:###
  75. $parms{text} = &set_voice($parms{voice}, $parms{text}) if $parms{voice};
  76. $parms{text} = &set_volume($parms{volume}, $parms{text}) if $parms{volume};
  77. $parms{text} = &set_pitch($parms{pitch}, $parms{text}) if $parms{pitch};
  78. $parms{text} = force_pronounce($parms{text}) if %pronouncable;
  79. # Only MSVoice currently tells us when it is done
  80. # For all others, set a timer with a rough guess
  81. set $is_speaking_timer (1 + (length $parms{text}) / 10) unless $VTxt;
  82. unless ($VTxt or $speak_pgm or $VTxt_festival or $ViaVoiceTTS) {
  83. unless ($main::config_parms{voice_text}) {
  84. print "Can not speak. mh.ini entry for voice_text is disabled. Phrase=$parms{text}\n";
  85. } else {
  86. print "Can not speak. Voice_Text object failed to create. Phrase=$parms{text}\n";
  87. }
  88. return;
  89. }
  90. if ($VTxt_festival) {
  91. #<SABLE>
  92. #<SPEAKER NAME="male1">
  93. #<VOLUME LEVEL="loud">
  94. #<RATE SPEED="-10%">
  95. # text
  96. #</RATE>
  97. #</VOLUME>
  98. #</SPEAKER>
  99. #</SABLE>
  100. if ($parms{voice} or $parms{volume} or $parms{rate}) {
  101. my $prefix = qq[<SABLE>];
  102. my $suffix = qq[</SABLE>];
  103. if ($parms{voice}) {
  104. $prefix .= qq[<SPEAKER NAME="$parms{voice}">];
  105. $suffix = qq[</SPEAKER>] . $suffix;
  106. }
  107. if ($parms{volume}) {
  108. $prefix .= qq[<VOLUME LEVEL="$parms{volume}">];
  109. $suffix = qq[</VOLUME>]. $suffix;
  110. }
  111. if ($parms{rate}) {
  112. $prefix .= qq[<RATE SPEED="$parms{rate}">];
  113. $suffix = qq[</RATE>] . $suffix;
  114. }
  115. $parms{text} = $prefix . $parms{text} . $suffix;
  116. }
  117. print "Data sent to festival: $parms{text}\n";
  118. set $VTxt_festival qq[(SayText "$parms{text}")];
  119. }
  120. if ($ViaVoiceTTS or $main::config_parms{voice_text} =~ /vv_tts/i) {
  121. $parms{voice} = $main::config_parms{viavoice_voice} unless $parms{voice};
  122. my %voice_table = (male => 1, female => 2, child => 3, elder_female => 7, elder_male => 8);
  123. $parms{voice} = $voice_table{lc $parms{voice}} if $voice_table{lc $parms{voice}};
  124. $parms{text} =~ s/\"/\'/g;
  125. }
  126. if ($speak_pgm) {
  127. my $self = {};
  128. my $pid = fork;
  129. $SIG{CHLD} = "IGNORE"; # eliminate zombies created by FORK()
  130. if ($pid) {
  131. $$self{pid} = $pid;
  132. } elsif (defined $pid) {
  133. my $speak_pgm_arg = '';
  134. if (my $file = $parms{play}) {
  135. unless ($file =~ /^System/ or $file =~ /^[\\\/]/ or $file =~ /^\S\:/) {
  136. $file = "$main::config_parms{sound_dir}/$parms{play}";
  137. $file = "$main::config_parms{sound_dir_common}/$parms{play}" unless -e $file;
  138. }
  139. $speak_pgm_arg .= " -play $file ";
  140. }
  141. if ($main::config_parms{voice_text} =~ /vv_tts/i) {
  142. $speak_pgm_arg .= " -text '`v$parms{voice} $parms{text}'";
  143. }
  144. else {
  145. $speak_pgm .= ' -volume ' . $parms{volume} if $parms{volume};
  146. $speak_pgm .= ' -pitch ' . $parms{pitch} if $parms{pitch};
  147. $speak_pgm .= ' -voice ' . $parms{voice} if $parms{voice};
  148. $speak_pgm_arg .= qq[ "$parms{text}"];
  149. }
  150. print "db start TTS: $speak_pgm $speak_pgm_arg\n" if $main::config_parms{debug} eq 'voice';
  151. exec qq[$speak_pgm $speak_pgm_arg];
  152. die 'cant exec $speak_pgm';
  153. }
  154. }
  155. if ($ViaVoiceTTS) {
  156. $SIG{CHLD} = "IGNORE"; # eliminate zombies created by FORK()
  157. FORK: # straight out of the book
  158. if (my $pid=fork) { # if forked ok
  159. # Parent's code
  160. print "$parms{text} sent to $pid\n";
  161. } elsif (defined $pid) {
  162. # child's code here
  163. my $prog = <<ProgCode;
  164. use ViaVoiceTTS;
  165. my \$tts = new ViaVoiceTTS();
  166. ViaVoiceTTS::setVoice \$tts,"$parms{voice}";
  167. ViaVoiceTTS::speak \$tts,"$parms{text}";
  168. exit 0;
  169. ProgCode
  170. exec "echo '$prog' | $^X "; # pipe prog to perl
  171. die "ViaVoiceTTS child died"; # This statement should not be reached
  172. } elsif ($! =~ /No more process/) {
  173. # EAGAIN, supposedly recoverable fork error
  174. sleep 2;
  175. redo FORK;
  176. } else {
  177. # weird fork error
  178. die "Can't fork: $!\n";
  179. }
  180. }
  181. if ($VTxt and $parms{text}) {
  182. print "Voice_Text.pm ms_tts: VTxt=$VTxt text=$parms{'text'}\n" if $main::config_parms{debug} eq 'voice';
  183. if ($VTxt_version eq 'msv5') {
  184. # Allow option to save speech to a wav file
  185. if ($parms{to_file}) {
  186. my $stream = Win32::OLE->new('Sapi.SpFileStream');
  187. $stream->Open($parms{to_file}, 3, 0);
  188. $VTxt->{AudioOutputStream} = $stream;
  189. $VTxt->Speak($parms{text}, 8); # Flags: 8=XML (no async, so we can close)
  190. $stream->Close;
  191. &main::print_log("Text->wav file: $parms{to_file}");
  192. &main::play($parms{to_file});
  193. }
  194. else {
  195. # $VTxt->Speak($parms{text}, 1 + 2 + 8); # Flags: 1=async 2=pruge 8=XML
  196. $VTxt->Speak($parms{text}, 1 + 8);
  197. }
  198. }
  199. # Older engine
  200. else {
  201. # Turn off vr while speaking ... SB live card will listen while speaking!
  202. # - this doesn't work. TTS does not start right away. Best to poll in Voice_Cmd
  203. # &Voice_Cmd::deactivate;
  204. my(%priority) = ('normal' => hex(200), 'high' => hex(100), 'veryhigh' => hex(80));
  205. my(%type) = ('statement' => hex(1), 'question' => hex(2), 'command' => hex(4),
  206. 'warning' => hex(8), 'reading' => hex(10), 'numbers' => hex(20),
  207. 'spreadsheet' => hex(40));
  208. $parms{type} = 'statement' unless $parms{'type'};
  209. $parms{speed} = 170 unless $parms{'speed'};
  210. $parms{priority} = 'normal' unless $parms{priority};
  211. $priority{$parms{'priority'}} = $parms{'priority'} if $parms{'priority'} =~ /\d+/; # allow for direct parm
  212. # $VTxt->{'Speed'} = $parms{'speed'} if defined $parms{'speed'};
  213. my ($priority, $type, $voice);
  214. $priority = $priority{$parms{'priority'}};
  215. $type = $type{$parms{'type'}};
  216. $voice = qq[\\Vce=Speaker="$parms{voice}"\\] if $parms{voice};
  217. $voice = '' unless $voice;
  218. print "Voice_Text.pm ms_tts: VTxt=$VTxt text=$parms{'text'}\n" if $main::config_parms{debug} eq 'voice';
  219. $VTxt->Speak($voice . $parms{'text'}, $priority);
  220. # $VTxt->Speak($parms{'text'}, ($priority | $type));
  221. # $VTxt->Speak('Hello \Chr="Angry"\ there. Bruce is \Vce=Speaker=Biff\ a very smart idiot guy.', hex('201'));
  222. }
  223. }
  224. }
  225. sub is_speaking {
  226. # print " vt=$VTxt .. ";
  227. if ($VTxt) {
  228. if ($VTxt_version eq 'msv5') {
  229. return 2 == ($VTxt->Status->{RunningState});
  230. }
  231. else {
  232. return $VTxt->{IsSpeaking};
  233. }
  234. }
  235. else {
  236. return active $is_speaking_timer;
  237. }
  238. }
  239. # This has been moved to mh. Leave this stub in so
  240. # we don't break old user code
  241. sub last_spoken {
  242. my ($how_many) = @_;
  243. &main::speak_log_last($how_many);
  244. }
  245. sub read_pronouncable_list {
  246. my($pronouncable_list_file) = @_;
  247. my ($phonemes, $word, $cnt);
  248. open (WORDS, $pronouncable_list_file) or print "\nError, could not find the pronouncable word file $pronouncable_list_file: $!\n";
  249. undef %pronouncable;
  250. while (<WORDS>) {
  251. next if /^\#/;
  252. ($word, $phonemes) = $_ =~ /^(\S+)\s+(.+)\s*$/;
  253. next unless $word;
  254. $cnt++;
  255. $pronouncable{$word} = $phonemes;
  256. }
  257. print "Read $cnt entries from $pronouncable_list_file\n";
  258. close WORDS;
  259. }
  260. sub set_mode {
  261. my ($mode) = lc shift;
  262. # Only MS TTS for now
  263. if ($VTxt) {
  264. if ($VTxt_version eq 'msv5') {
  265. return $VTxt->Skip('Sentence',99999) if $mode eq 'stop';
  266. return $VTxt->Pause if $mode eq 'pause';
  267. return $VTxt->Resume if $mode eq 'resume';
  268. return $VTxt->Skip('Sentence', 5) if $mode eq 'fastforward';
  269. return $VTxt->Skip('Sentence', -5) if $mode eq 'rewind';
  270. return $VTxt->Skip('Sentence', $1) if $mode =~ /forward_(\d+)/;
  271. return $VTxt->Skip('Sentence', -$1) if $mode =~ /rewind_(\d+)/;
  272. }
  273. else {
  274. return $VTxt->StopSpeaking if $mode eq 'stop';
  275. return $VTxt->AudioPause if $mode eq 'pause';
  276. return $VTxt->AudioResume if $mode eq 'resume';
  277. return $VTxt->AudioFastForward if $mode eq 'fastforward';
  278. return $VTxt->AudioRewind if $mode eq 'rewind';
  279. }
  280. }
  281. }
  282. sub set_pitch {
  283. my ($pitch, $text) = @_;
  284. # Only MS TTS v5 for now
  285. if ($VTxt_version eq 'msv5') {
  286. # Only xml support, so only for the specified text
  287. if ($text) {
  288. return "<pitch absmiddle='$pitch'/> " . $text;
  289. }
  290. else {
  291. print "\nError, no support for setting the default pitch\n";
  292. return;
  293. }
  294. }
  295. }
  296. sub set_rate {
  297. my ($rate, $text) = @_;
  298. # Only MS TTS for now
  299. return unless $VTxt;
  300. if ($VTxt_version eq 'msv4') {
  301. return $VTxt->{Speed} = 250 if $rate eq 'fast';
  302. return $VTxt->{Speed} = 200 if $rate eq 'normal';
  303. return $VTxt->{Speed} = 150 if $rate eq 'slow';
  304. return $VTxt->{Speed} = $rate if $rate =~ /^\d+$/
  305. }
  306. else {
  307. $rate = 4 if $rate eq 'fast';
  308. $rate = 0 if $rate eq 'normal';
  309. $rate = -4 if $rate eq 'slow';
  310. # If text is given, set for just this text with XML. Otherwise change the default
  311. if ($text) {
  312. return "<rate absspeed='$rate'/> " . $text;
  313. }
  314. else {
  315. $VTxt->{Rate} = $rate;
  316. return;
  317. }
  318. }
  319. }
  320. sub set_volume {
  321. my ($volume, $text) = @_;
  322. # Only MS TTS v5 for now
  323. if ($VTxt_version eq 'msv5') {
  324. # If text is given, set for just this text with XML. Otherwise change the default
  325. if ($text) {
  326. return "<volume level='$volume'/> " . $text;
  327. }
  328. else {
  329. $VTxt->{Volume} = $volume;
  330. return;
  331. }
  332. }
  333. }
  334. sub set_voice {
  335. my ($voice, $text) = @_;
  336. $voice = lc $voice;
  337. # Only MS TTS v5 for now
  338. if ($VTxt_version eq 'msv5') {
  339. my $spec;
  340. $voice = lc $voice;
  341. if ($voice =~ /female/) {
  342. $spec .= "Gender=Female;";
  343. }
  344. elsif ($voice =~ /male/) {
  345. $spec .= "Gender=Male;";
  346. }
  347. if ($voice =~ /child/) {
  348. $spec .= "Age=Child;";
  349. }
  350. elsif ($voice =~ /grownup/) {
  351. $spec .= "Age=!Child;";
  352. }
  353. if ($voice =~ /random/) {
  354. my (@voices, $object);
  355. # @voices = Win32::OLE::in $VTxt->GetVoices($spec);
  356. # Filter out unusual voices
  357. for $object (Win32::OLE::in $VTxt->GetVoices($spec)) {
  358. next if $object->GetDescription eq 'Sample TTS Voice';
  359. next if $object->GetDescription eq 'MS Simplified Chinese Voice';
  360. push @voices, $object;
  361. }
  362. my $i = int((@voices) * rand);
  363. $object = $voices[$i];
  364. $spec = "Name=" . $object->GetDescription;
  365. print "Setting random voice. i=$i spec=$spec\n";
  366. }
  367. unless ($spec) {
  368. $spec = "Name=Microsoft $voice";
  369. }
  370. print "Setting ms voice to spec=$spec\n";
  371. # If text is given, set for just this text with XML. Otherwise change the default
  372. if ($text) {
  373. return "<voice required='$spec'/> " . $text;
  374. }
  375. # First voice returned is the best fit
  376. else {
  377. for my $object (Win32::OLE::in $VTxt->GetVoices($spec)) {
  378. print "Setting voice for $spec: $object\n";
  379. $VTxt->{Voice} = $object;
  380. return;
  381. }
  382. }
  383. }
  384. }
  385. sub force_pronounce {
  386. my($phrase) = @_;
  387. print "input phrase is '$phrase'\n" if $main::config_parms{debug} eq 'voice';
  388. for my $word (keys %pronouncable) {
  389. $phrase =~ s/\b$word\b/$pronouncable{$word}/gi;
  390. }
  391. print "output phrase is '$phrase'\n" if $main::config_parms{debug} eq 'voice';
  392. return $phrase;
  393. }
  394. 1;
  395. #
  396. # $Log$
  397. # Revision 1.28 2001/09/23 19:28:11 winter
  398. # - 2.59 release
  399. #
  400. # Revision 1.27 2001/08/12 04:02:58 winter
  401. # - 2.57 update
  402. #
  403. # Revision 1.26 2001/05/28 21:14:38 winter
  404. # - 2.52 release
  405. #
  406. # Revision 1.25 2001/05/06 21:07:26 winter
  407. # - 2.51 release
  408. #
  409. # Revision 1.24 2001/03/24 18:08:38 winter
  410. # - 2.47 release
  411. #
  412. # Revision 1.23 2001/02/24 23:26:40 winter
  413. # - 2.45 release
  414. #
  415. # Revision 1.22 2001/02/04 20:31:31 winter
  416. # - 2.43 release
  417. #
  418. # Revision 1.21 2000/09/09 21:19:11 winter
  419. # - 2.28 release
  420. #
  421. # Revision 1.20 2000/08/19 01:22:36 winter
  422. # - 2.27 release
  423. #
  424. # Revision 1.19 2000/05/06 16:34:32 winter
  425. # - 2.15 release
  426. #
  427. # Revision 1.18 2000/04/09 18:03:19 winter
  428. # - 2.13 release
  429. #
  430. # Revision 1.17 2000/02/20 04:47:55 winter
  431. # -2.01 release
  432. #
  433. # Revision 1.16 2000/01/27 13:44:27 winter
  434. # - update version number
  435. #
  436. # Revision 1.15 2000/01/13 13:39:52 winter
  437. # - added mixer_settings and vvo_stuff (added 2 weeks ago)
  438. #
  439. # Revision 1.12 1999/10/09 20:38:37 winter
  440. # - add max_log_entries check
  441. #
  442. # Revision 1.11 1999/05/30 21:08:55 winter
  443. # - change TDstamp format in log
  444. #
  445. # Revision 1.10 1999/02/21 00:27:17 winter
  446. # - use $OS_win
  447. #
  448. # Revision 1.9 1999/02/04 14:21:28 winter
  449. # - switch to new OLE calls. Add better error checking
  450. #
  451. # Revision 1.8 1999/01/22 02:43:21 winter
  452. # - add Festival support.
  453. #
  454. # Revision 1.7 1999/01/10 02:29:50 winter
  455. # - give better 'tts engine disabled' messages
  456. #
  457. # Revision 1.6 1999/01/09 21:43:14 winter
  458. # - improve ole fail error
  459. #
  460. # Revision 1.5 1999/01/07 01:55:03 winter
  461. # - Limit size of Spoken_Text array
  462. #
  463. # Revision 1.4 1998/12/08 02:26:07 winter
  464. # - add log
  465. #
  466. #