Now, in FlightGear, enable ATC (in the menu under "ATC"->"Options"), press the '-key (apostrophe key) and send a message to the ATC. Hear "your" voice, that of the ATC, and some time later that of AI-planes.$ festival --server & $ fgfs --aircraft=j3cub --airport=KSQL --prop:/sim/sound/voices/enabled=true
$ festival festival> (SayText "FlightGear") festival> (quit)
$ mbrola -h
$ festival festival> (print (mapcar (lambda (pair) (car pair)) voice-locations)) (kal_diphone rab_diphone don_diphone us1_mbrola us2_mbrola us3_mbrola en1_mbrola) nil festival> (voice_us3_mbrola) festival> (SayText "I've got a nice voice.") festival> (quit)
$ festival --server
Of course, you can put this option into your personal configuration file. This doesn't mean that you then always have to use FlightGear together with Festival. You'll just get a few error messages in the terminal window, but that's it. Note that you can currently not enable the voice subsystem at runtime!$ fgfs --aircraft=j3cub --airport=KSQL --prop:/sim/sound/voices/enabled=true
<sim>
<voices>
<host type="string">localhost</host>
<port type="string">1314</port>
<enabled type="bool">false</enabled>
<voice>
<desc>Pilot</desc>
<text type="string"></text>
<volume type="double">1.0</volume>
<pitch type="double">100.0</pitch>
<speed type="double">1.0</speed>
<preamble type="string">(voice_us3_mbrola)</preamble>
<festival type="bool">true</festival>
</voice>
<voice>
...
</voice>
<!-- handy aliases, not part of the interface: -->
<atc alias="/sim/sound/voices/voice[0]/text"/>
<approach alias="/sim/sound/voices/voice[0]/text"/>
<ground alias="/sim/sound/voices/voice[0]/text"/>
<pilot alias="/sim/sound/voices/voice[1]/text"/>
<copilot alias="/sim/sound/voices/voice[2]/text"/>
<ai-plane alias="/sim/sound/voices/voice[3]/text"/>
</voices>
</sim>
The <enabled> property decides at init time whether the subsystem should
be activated or not. There's currently no way to change this at runtime.
Each <voice> group defines one channel. <text> is the output
property. Every value that's written to it will be spoken by this channel.
If <festival> is true, then the channel will set up <pitch> and
<speed> (<volume> does currently not work and has to be 1),
and puts Festival markup around the text. If <festival> is false,
then all text is written verbatim to the socket. <preamble> is always
written to the socket once as last step of the socket creation. In "festival"
mode it's used to set the voice, while in raw mode it could be used to identify
the channel (assuming that the server knows what to do with it).
<sim>
<voices>
<host type="string">192.168.2.15</host>
<port type="string">7100</port>
<enabled type="bool">true</enabled>
<voice>
<desc>ATC/Approach/Ground</desc>
<text type="string"></text>
<preamble type="string">ATC</preamble>
<festival type="bool">false</festival>
</voice>
<voice>
<desc>Pilot</desc>
<text type="string"></text>
<preamble type="string">Pilot</preamble>
<festival type="bool">false</festival>
</voice>
...
</voices>
</sim>
<volume>, <pitch>, and <speed> have no meaning and can
be left away. Note that also in this mode the preamble gets sent first.
It can be used to identify the channel. Of course, all messages could be
sent to just one channel, though.
#!/usr/bin/perl -Tw
# License: GPL V2
# Modified after Example from perlipc.pod ($ man perlipc)
use strict;
BEGIN {
$ENV{PATH} = '/usr/ucb:/bin';
}
use Socket;
use Carp;
my $EOL = "\015\012";
sub spawn; # forward declaration
sub logmsg {
print "$0 $$: @_ at ", scalar localtime, "\n";
}
my $port = shift || 1314;
my $proto = getprotobyname('tcp');
($port) = $port =~ /^(\d+)$/ or die "invalid port";
socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!";
setsockopt(Server, SOL_SOCKET, SO_REUSEADDR, pack("l", 1)) || die "setsockopt: $!";
bind(Server, sockaddr_in($port, INADDR_ANY)) || die "bind: $!";
listen(Server,SOMAXCONN) || die "listen: $!";
logmsg "server started on port $port";
my $waitedpid = 0;
my $paddr;
use POSIX ":sys_wait_h";
sub REAPER {
my $child;
while (($waitedpid = waitpid(-1,WNOHANG)) > 0) {
logmsg "reaped $waitedpid" . ($? ? " with exit $?" : '');
}
$SIG{CHLD} = \&REAPER; # loathe sysV
}
$SIG{CHLD} = \&REAPER;
for ($waitedpid = 0;
($paddr = accept(Client,Server)) || $waitedpid;
$waitedpid = 0, close Client) {
next if $waitedpid and not $paddr;
my($port,$iaddr) = sockaddr_in ($paddr);
my $name = gethostbyaddr($iaddr,AF_INET);
logmsg "connection from $name [", inet_ntoa($iaddr), "] at port $port";
spawn sub {
$|=1;
print "Hello there, $name, it's now ", scalar localtime, $EOL;
exec '/usr/bin/fortune' # XXX: `wrong' line terminators
or confess "can't exec fortune: $!";
};
}
sub spawn
{
my $coderef = shift;
unless (@_ == 0 && $coderef && ref($coderef) eq 'CODE') {
confess "usage: spawn CODEREF";
}
my $pid;
if (!defined($pid = fork)) {
logmsg "cannot fork: $!";
return;
} elsif ($pid) {
logmsg "creating child $pid";
return; # I'm the parent
}
# else I'm the child -- go spawn
# print header
my $id;
while (<Client>) {
s/^\s+//;
s/\s+$//;
# first line is voice channel id = "<preamble>"
if (not defined $id) {
$id = $_;
next;
}
print "\033[32m$id: \033[m$_\n";
last unless /\S/;
}
open(STDIN, "<&Client") || die "can't dup client to stdin";
open(STDOUT, ">&Client") || die "can't dup client to stdout";
## open(STDERR, ">&STDOUT") || die "can't dup stdout to stderr";
exit &$coderef();
}