清华大佬耗费三个月吐血整理的几百G的资源,免费分享!....>>>
#!/usr/bin/perl
# EmacSpeaks Festival
#
# A Emacspeak speech server for Festival written in Perl.
# Written by Mario Lang <mlang@delysid.org>
# Enhancements by Aaron Bingham <abingham@sfu.ca>
# The skeleton was taken from speechd (and modified alot of course).
use strict;
# Configuration
my $USE_ESD = 1;
# If nonzero use the Enlightened Sound Daemon for managing the
# Festival server, otherwise run Festival directly. Overridden by
# FESTIVAL_COMMAND
my $LOGFILE = '/tmp/festival.log';
# File to use for logging debugging messages.
my $FESTIVAL_HOST = 'localhost';
# Hostname where Festival server is running. If FESTIVAL_HOST is
# 'localhost', a Festival server process will be started
# automatically if none is currently running.
my $FESTIVAL_PORT = '1314';
# Port Festival server listens to.
my $FESTIVAL_BIN = '/usr/bin/festival';
# Path to Festival executable.
my $FESTIVAL_ARGS = '--server \'(set! server_access_list (list "localhost\.localdomain"))\'';
# Arguments for the Festival process. The default starts a server
# process accessible to any user on the local machine.
my $FESTIVAL_ASYNC = 1;
# Use Festival in asynchronous mode if nonzero, otherwise use
# synchronous mode. Asynchronous mode allows the client to stop
# in-progress speech, so the result is generally more responsive
# behavior. However, some users have complained that asynchronous
# mode causes too many utterances to be surpressed (e.g. letter
# names while typing fast). If this is a problem for you, set
# $FESTIVAL_ASYNC = 0.
my $SPEAKER_1 = 'rab_diphone';
my $SPEAKER_2 = 'kal_diphone';
# Name of the festival speakers to use. Change this to voices you have
# installed.
# Internal variables
my $FILE_STUFF_KEY = 'ft_StUfF_key'; # This indicates a new prompt from Festival
my $handle = undef; # this is for the tcp connection...
my $queued_text = '';
my $FESTIVAL_COMMAND;
if ($USE_ESD) {
$FESTIVAL_COMMAND = "esddsp $FESTIVAL_BIN $FESTIVAL_ARGS &";
} else {
$FESTIVAL_COMMAND = "$FESTIVAL_BIN $FESTIVAL_ARGS &";
}
#includes libs for TCP socket connection to Festival
use IO::Socket;
my $err = 0;
my $pronounce_punctuation = 0;
my $emacs_in_braces = 0;
my @pq_queues = ([], []);
my %sable_params = ('speaker'=>$SPEAKER_1, 'rate'=>1, 'base'=>150,
'range'=>10, 'mid'=>1);
my $festival_busy = 0;
my $emacs_lines = 0;
my $line_number = 0;
sub main {
my $emacstext = '';
my $festtext = '';
&log("INIT: FESTIVAL_COMMAND = $FESTIVAL_COMMAND\n");
# create a tcp connection to the festival server
&connect_to_festival();
&log("INIT: Starting loop.\n");
# If Festival closes the connection, try to reconnect
local $SIG{PIPE} = \&connect_to_festival;
my $info;
while (1) {
my $rin;
vec($rin, fileno(STDIN), 1) = 1;
vec($rin, fileno($handle), 1) = 1;
select($rin, undef, undef, undef);
if (vec($rin, fileno(STDIN), 1)) {
my $buf;
sysread STDIN,$buf,1024;
if (!$buf) {
&log("Unexpected EOF in STDIN\n");
exit 1;
}
$emacstext .= $buf;
$emacstext = &handle_emacs_input($emacstext);
}
if (vec($rin, fileno($handle), 1)) {
my $buf;
sysread $handle,$buf,1024;
if (!$buf) {
&log("Unexpected EOF in Festival socket\n");
exit 1;
}
$festtext .= $buf;
$festtext = &handle_festival_input($festtext);
}
&log("\$festival_busy = $festival_busy\n");
while (!$festival_busy && !&pq_empty()) {
&send_command;
}
}
}
sub log {
# Write a message to the logfile
my $msg = shift;
open(LOG, ">>$LOGFILE") or die "Cant open logfile\n";
print LOG $msg;
close LOG;
}
sub handle_emacs_input {
my $emacstext = shift;
while (1) {
my ($line, $eol, $remainder) = split /(\n)/, $emacstext, 2;
last if !$eol;
$emacstext = $remainder;
&handle_emacs_line($line . $eol);
}
return $emacstext;
}
sub handle_emacs_line {
my $line = shift;
&log("emacspeak: $line");
$emacs_lines .= $line;
if ($line =~ /{[^}]*\n/) {
$emacs_in_braces = 1;
} elsif ($line =~ /}/) {
$emacs_in_braces = 0;
}
if (!$emacs_in_braces) {
handle_emacs_command($emacs_lines);
$emacs_lines = '';
}
}
sub handle_festival_input {
my $festtext = shift;
while (1) {
my ($line, $eol, $remainder) = split /(\n)/, $festtext, 2;
last if !$eol;
$festtext = $remainder;
&handle_festival_line($line . $eol);
}
return $festtext;
}
sub handle_festival_line {
my $text = shift;
&log("festival: $text");
if ($text =~ /^$FILE_STUFF_KEY/) {
$festival_busy = 0;
} elsif ($text =~ /^(OK|ER)/) {
# the festival session is in a wierd state. reconnect.
&log("the festival is in a wierd state. reconnect.\n");
&send_to_festival("(quit)");
&connect_to_festival();
}
}
sub handle_emacs_command {
my $text = shift;
if ($text =~ /^\s*exit\n/) {
&quit();
} elsif ($text =~ /^\s*q\s+\{(.*)}/s) {
&queue_speech($1);
} elsif ($text =~ /^\s*t\s+(\d+)\s(\d+)/) {
&tone($1, $2);
} elsif ($text =~ /^\s*p\s+(.*)\n/) {
&play_sound($1);
} elsif ($text =~ /^\s*d/) {
&flush_speech();
} elsif ($text =~ /^\s*tts_say \{(.*)\}/) {
&say($1);
} elsif ($text =~ /^\s*l \{([^}])\}/) {
&letter($1);
} elsif ($text =~ /^\s*s/) {
&stop();
} elsif ($text =~ /^\s*tts_set_punctuations (\w+)/) {
&set_punctuation($1);
} elsif ($text =~ /^\s*tts_set_speech_rate (\d+)/) {
&set_speech_rate($1)
} elsif ($text =~ /^\s*tts_sync_state (\w+) (\d+) (\d+) (\d+) (\d+)/) {
&set_punctuation($1);
&set_speech_rate($5);
} else {
$err++;
&log("$line_number: err$err: $text\n");
}
return 1;
}
# Actions
sub quit {
&pq_clear();
&stop();
&send_to_festival("(quit)");
exit 0;
}
sub tone {
my $pitch = shift; # pitch in Hz
my $duration = shift; # duration in ms
# Run asynchronously for better responsiveness
system("beep -f $pitch -l $duration &");
}
sub play_sound {
my $filename = shift;
my $url = "file://" . &url_quote($filename);
&send_sable("<SABLE><AUDIO SRC=\"$url\"/></SABLE>");
}
sub flush_speech {
# Flush all queued speech to the speech generator.
if ($queued_text ne '') {
speak($queued_text);
}
$queued_text = '';
}
sub letter {
my $char = shift;
my $content = &sgml_quote($char);
&send_sable("<SABLE><SAYAS MODE=\"literal\">$content</SAYAS></SABLE>");
}
sub set_punctuation {
my $mode = shift;
if ($1 eq "all") {
$pronounce_punctuation = 1;
} else {
$pronounce_punctuation = 0;
}
}
sub set_speech_rate {
my $dtk_rate = shift;
# 225.0 was picked as it gives reasonable behavior. I do not
# know if the result is slower or faster than the DECTalk.
$sable_params{'rate'} = $dtk_rate/225.0;
}
sub stop {
# The queue must be cleared immediately so that any queued
# commands recieved before the stop do not get run afterwards.
&pq_clear();
$queued_text = '';
if ($FESTIVAL_ASYNC) {
&send_to_festival("(audio_mode 'shutup)");
}
}
sub queue_speech {
# Save speech to be sent later.
my $text = shift;
$queued_text .= $text;
}
sub say {
my $text = shift;
&speak($text);
}
sub speak {
my $text = shift;
if ($text =~ /\S+/) {
foreach my $sable (&dtk_to_sable($text)) {
&send_sable($sable);
}
} else {
&log("$line_number: Empty queue, nothing sent. \n");
}
}
sub send_sable {
my $sable = shift;
$sable =~ s/"/\\"/g;
&send_to_festival("(tts_text \"$sable\" 'sable)");
}
sub dtk_to_sable {
my $dtk = shift;
my @items = &dtk_parse($dtk);
# '[*]' seems to be used as an alternative (perhaps shorter) to
# a space character.
$dtk =~ s/\[\*\]/ /;
my @sable_docs = ();
foreach my $item (@items) {
my $type = $item->[0];
my $value = $item->[1];
&log("($type, $value)\n");
if ($type eq 'TEXT') {
push @sable_docs, &text_to_sable($value);
} elsif ($type eq 'COMMAND') {
$value =~ s/\s+//; # trim leading whitespace
&handle_dtk_command(split /\s+/, $value);
}
}
return @sable_docs;
}
sub dtk_parse {
my $dtk = shift;
# Return a list of (type, value) tuples, where type is either
# COMMAND or TEXT.
my $in_command = 0;
my @items = ();
my $value = '';
for (my $i = 0; $i <= length $dtk; $i++) {
my $ch = substr $dtk, $i, 1;
if ($ch eq '[') {
if (!$in_command) {
if ($value ne '') {
my @item = ('TEXT', $value);
push @items, \@item;
}
} else {
&log("ERROR: [ found while looking for ]\n");
}
$in_command = 1;
$value = '';
} elsif ($ch eq ']') {
if ($in_command) {
if ($value ne '') {
my @item = ('COMMAND', $value);
push @items, \@item;
}
} else {
&log("ERROR: ] found while looking for [\n");
}
$in_command = 0;
$value = '';
} else {
$value .= $ch;
}
}
if ($value ne '' && !$in_command) {
my @item = ('TEXT', $value);
push @items, \@item;
}
if ($in_command) {
&log("ERROR: ] expected\n");
}
return @items;
}
sub handle_dtk_command {
my @cmdlist = @_;
&log ("cmdlist: @cmdlist\n");
&log ("cmdlist[0]: $cmdlist[0]\n");
if ($cmdlist[0] =~ /:np/) {
$sable_params{'speaker'} = $SPEAKER_1;
$sable_params{'base'} = 100;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nh/) {
$sable_params{'speaker'} = $SPEAKER_2;
$sable_params{'base'} = 100;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nd/) {
$sable_params{'speaker'} = $SPEAKER_1;
$sable_params{'base'} = 150;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nf/) {
$sable_params{'speaker'} = $SPEAKER_2;
$sable_params{'base'} = 150;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nb/) {
$sable_params{'speaker'} = $SPEAKER_1;
$sable_params{'base'} = 200;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nu/) {
$sable_params{'speaker'} = $SPEAKER_2;
$sable_params{'base'} = 200;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nr/) {
$sable_params{'speaker'} = $SPEAKER_1;
$sable_params{'base'} = 300;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nw/) {
$sable_params{'speaker'} = $SPEAKER_2;
$sable_params{'base'} = 300;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:nk/) {
$sable_params{'speaker'} = $SPEAKER_1;
$sable_params{'base'} = 400;
$sable_params{'range'} = 10;
} elsif ($cmdlist[0] =~ /:dv/) {
&log ("cmdlist (dv): @cmdlist\n");
for (my $j = 1; $j <= $#cmdlist; $j+=2) {
print "param: $cmdlist[$j] $cmdlist[$j+1]\n";
if ($cmdlist[$j] =~ /ap/) {
# Average pitch (Hz)
$sable_params{'base'} = $cmdlist[$j+1];
}
if ($cmdlist[$j] =~ /pr/) {
# Pitch range. The Dectalk parameter ranges between 0
# -- a flat monnotone -- and 250 -- a highly animated
# voice.
$sable_params{'range'} = 2.0*sqrt(5.0*$cmdlist[$j+1]);
}
}
}
}
sub text_to_sable {
my $text = shift;
# Convert a string of plain text to a SABLE document, using the current
# parameters.
if ($pronounce_punctuation) {
$text =~ s/[`]/ backquote /g;
$text =~ s/[!]/ bang /g;
$text =~ s/[(]/ left paren /g;
$text =~ s/[)]/ right paren /g;
$text =~ s/[-]/ dash /g;
$text =~ s/[{]/ left brace /g;
$text =~ s/[}]/ right brace /g;
$text =~ s/[:]/ colon /g;
$text =~ s/[;]/ semi /g;
$text =~ s/["]/ quotes /g;
$text =~ s/[']/ apostrophe /g;
$text =~ s/[,]/ comma /g;
$text =~ s/[.]/ dot /g;
$text =~ s/[?]/ question /g;
}
# escape SGML-unsafe characters
$text = sgml_quote($text);
return <<HERE
<SABLE>
<SPEAKER NAME="$sable_params{'speaker'}">
<RATE SPEED="$sable_params{'rate'}">
<PITCH BASE="$sable_params{'base'}"
RANGE="$sable_params{'range'}"
MID="$sable_params{'mid'}">
$text
</PITCH>
</RATE>
</SPEAKER>
</SABLE>
HERE
}
sub sgml_quote {
my $text = shift;
$text =~ s/&/&/g;
$text =~ s/</</g;
$text =~ s/>/>/g;
return $text;
}
sub url_quote {
# XXX: incomplete!
my $text = shift;
$text =~ s/ /%20/;
return $text;
}
sub send_to_festival {
my $command = shift;
# queue $command for sending to Festival
&pq_push($command, 1);
}
sub send_command {
# send a single queued command
if ($handle) { # Sanity checks are always nice...
&send_direct(&pq_pop());
} else {
&connect_to_festival;
}
}
sub send_direct {
my $command = shift;
# send $command to festival immediately, without affecting the queue
&log("$line_number: festival> " . $command . "\n");
print($handle $command . "\n") or die "Could not write to Festival ($!)\n";
$festival_busy = 1;
}
sub connect_to_festival
{
my $tries = 0;
my $MAX_RECONNECT_TRIES = 10;
$handle = '';
while ($handle eq '' and $tries < $MAX_RECONNECT_TRIES)
{
&log("($tries) Attempting to connect to the Festival server.\n");
if ($handle = IO::Socket::INET->new(Proto => 'tcp',
PeerAddr => $FESTIVAL_HOST,
PeerPort => $FESTIVAL_PORT))
{
&log("Successfully opened connection to Festival.\n");
} else
{
if ($tries)
{
&log("Waiting for Festival server to load -- Can't connect to port $FESTIVAL_PORT on $FESTIVAL_HOST yet ($!).\n");
} else
{
if ($FESTIVAL_HOST eq 'localhost') {
&log("Failed to connect to Festival server, attempting to load it myself.\n");
system ($FESTIVAL_COMMAND);
}
}
sleep 1;
}
$tries++;
}
if ($handle eq '') {
die "ERROR: can't connect to Festival server!";
}
$handle->autoflush(1); # so output gets there right away
$festival_busy = 0;
if ($FESTIVAL_ASYNC) {
# Set festival to async mode. We have to call send_direct here to
# ensure that no commands from emacspeak hava a chance to get
# executed before this one.
&send_direct("(audio_mode 'async)");
}
}
#
# priority queue implementation
#
sub pq_push {
my $item = shift;
my $pri = shift;
push @{$pq_queues[$pri]}, $item;
}
sub pq_pop {
for (my $i = 0; $i <= $#pq_queues; $i++) {
if ($#{$pq_queues[$i]} >= 0) {
my $item = shift @{$pq_queues[$i]};
return $item;
}
}
}
sub pq_clear {
for (my $i = 0; $i <= $#pq_queues; $i++) {
$pq_queues[$i] = [];
}
}
sub pq_empty {
for (my $i = 0; $i <= $#pq_queues; $i++) {
if ($#{$pq_queues[$i]} >= 0) {
return 0;
}
}
return 1;
}
&main();
__END__
=head1 NAME
festival-server - Emacspeak server for the Festival speech synthesizer
=head1 SYNOPSIS
speechd
=head1 DESCRIPTION
festival-server is a perl script doing Emacspeak server syntax
to festival conversion.
This method makes use of the SABLE markup mode of festival.
Tones are supported if you have beep installed.
=head1 OPTIONS
=head1 FILES
=over 4
=item
/usr/share/emacs/site-lisp/emacspeak/festival-server