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
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">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>
<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.<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>
#!/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(); }