Board index FlightGear Development Nasal

Spoken ATC

Nasal is the scripting language of FlightGear.

Spoken ATC

Postby rleibner » Thu Jun 22, 2017 8:33 pm

Hi everyone,

I'm working on this atc.nas script, and it's time to submit to you the beta version.

This code is not intended to compete with FGComm, Festival or other similar facilities. On the contrary, their use is indicated when they are not available (eg, few hardware resources, no Internet connection, etc.)

1.- How to use it
Once installed, tune Comm1 to the desired frequency and press the ">" key to transmit your request.
You will hear the ATC's answer according the standard VFR phraseology, depending on your current position:
    * If you are on ground => The script interprets it's a taxi request (if you are far from the runway) or a take-off request (if you are on the correct Runway or at his holding point).
    * If you are flying => The script interprets it's an approach request (if you tunned an Approach freq.), or a landing request (if you tunned a Tower one).
Note that only Tower freqs can deliver landing or take-off clearances, and only Ground or Tower freqs can deliver taxiing ones.

2.- How to install it
Include theese lines into your $FGFS_ROOT/keyboard.xml file:
Code: Select all
 <key n="60">
  <name>less-than</name>
  <desc>Spoken ATC</desc>
  <binding>
   <command>nasal</command>
   <script>
     atc.speak()
     </script>
  </binding>
 </key>

and copy&paste this code into a new $FGFS_ROOT/Nasal/atc.nas file:
Code: Select all
# **     atc.nas (save in $FG_ROOT/Nasal)   **
# **         v.: 0.1 beta                   **
# ********************************************
# **    by Rodolfo Leibner (rleibner@gmail.com)   **
# ** Comments & enheacements are wellcome   **

 var speak = func() {
 
  # **** spell func. **************************
      var spell = func(str) {
      var s = split("",str);
      for(var i=0;i<size(s);i=i+1) {
        if(streq(s[i],"."))  s[i]="point" ;
      }
      return string.join(" ",s);
      };
  # **** abc func. *****************************
    var abc = func(str) {
    var alpha = {A:"alpha", B:"bravo", C:"charlie", D:"delta", E:"echo", F:"foxtrot",
                 G:"golf", H:"hotel", I:"india", J:"juliet", K:"kilo", L:"lima", M:"mike",
                 N:"november", O:"oscar", P:"papa",Q:"quebec", R:"romeo", S:"sierra",
                 T:"tango", U:"uniform", V:"victor", W:"whiskey", X:"X ray", Y:"yanki", Z:"zulu"};
    var s = split("",str);
    for(var i=0;i<size(s);i=i+1) {
      if(int(s[i])==nil)  s[i]=alpha[s[i]];   # Translate only letters
    };
    return string.join(" ",s);
    };
     
# *** Main ****************************************
# 1) Initialization
var a=call(func {
        return getprop("/instrumentation/comm/spk-atc");
    }, nil, nil, nil,);
if(a==nil) setprop("/instrumentation/comm/spk-atc", 0.0);

# 2) Check comm frequency
var cs = sprintf(getprop("/ai/models/aircraft/callsign"));
var icao = getprop("/instrumentation/comm/airport-id");   
var info = airportinfo(icao);
var qlty = getprop("/instrumentation/comm/signal-quality-norm");
var station_type = sprintf(getprop("/instrumentation/comm/station-type"));
var atc = string.replace(info.name,"Intl","International") ~" "~station_type;

if((atc==nil)or(qlty<0.01)) { # if invalid freq or out of range
    gui.popupTip("Check comm freq.!",3);
    die("Check comm freq.");}

# 3) Get env. values
var q =  sprintf("%.2f",getprop("/environment/metar/pressure-sea-level-inhg"));
var qnh = spell(sprintf("%d",q/0.02953));
var wd = sprintf("%d",getprop("/environment/metar/base-wind-dir-deg"));
var wv = sprintf("%d",getprop("/environment/metar/base-wind-speed-kt"));
var rws = info.runways;

# 4) choose best rwy
var best = "";
var ang = 180.0;
foreach(var rw; keys(rws)){
  var a = abs(info.runways[rw].heading - getprop("/environment/metar/base-wind-dir-deg"));
  if(a<ang) {
     ang = a;
     best = rw;}
  }

# 5) Check current position
var pos_v = (getprop("/position/altitude-agl-ft") < 30)? "onground": "flying";
var to = {lat: info.runways[rw].lat, lon: info.runways[rw].lon};
var (course, dist) = courseAndDistance(to);
var pos_h = ((dist * NM2M) < 100)? "onrwy": "far";

# 6) Choose pertinent instruction
var tunned = getprop("/instrumentation/comm/frequencies/selected-mhz");
var voice = (getprop("/instrumentation/comm/spk-atc")==tunned)? abc(cs): abc(cs)~ ". This is " ~atc  ;
var tip = "";

if(streq(pos_v,"onground")) {
   if(streq(pos_h,"far")) {   # Request for Taxi
      voice ~= ". Taxi to holding point Runway " ~spell(best) ~" and Contact when ready.";
      tip = "   Rwy: " ~best ;
   } elsif (!streq(station_type, "tower")) { # yet on rwy => Request for Takeoff
          voice ~= ". Contact Tower." ;
   } else {
       voice ~= ". Runway " ~spell(best) ~", cleared for takeoff, wind "~spell(wd) ~" degrees " ~spell(wv) ~" knots";
       tip = "   Wind: " ~wd ~" deg. " ~wv ~" kt.\n   Rwy: " ~best ~"\n   Cleared for takeoff";
   }
} elsif (streq(station_type, "tower")) {  # flying => Request for landing.
      voice ~= ". Runway " ~spell(best) ~", cleared to land, wind "~spell(wd) ~" degrees " ~spell(wv) ~" knots.";
      tip = "   Cleared to land " ~best ;
} elsif (string.match(station_type, "approach*")) { # Request for approach.
      voice ~". Make approach Runway " ~spell(best) ~", wind "~spell(wd) ~" degrees " ~spell(wv) ~" knots."
                  ~" QNH " ~qnh ~" or " ~q ~"inches and contact Tower.";
      tip = "   Approach Rwy " ~best ;
} else {
        voice ~= ". Contact Tower." ;
      }

# 7) Speach instruction
gui.popupTip(info.name ~":\n" ~tip,5);
setprop("/sim/sound/voices/atc", voice);
setprop("/instrumentation/comm/spk-atc", tunned);
};


I count on your collaboration to debug, improve and optimize this beta.
Regards,
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby Octal450 » Thu Jun 22, 2017 8:34 pm

Nice job!

Kind Regards,
Josh
Skillset: JSBsim Flight Dynamics, Systems, Canvas, Autoflight/Control, Instrumentation, Animations
Aircraft: A320-family, MD-11, MD-80, Contribs in a few others

Octal450's GitHub|Launcher Catalog
|Airbus Dev Discord|Octal450 Hangar Dev Discord
User avatar
Octal450
 
Posts: 5583
Joined: Tue Oct 06, 2015 1:51 pm
Location: Huntsville, AL
Callsign: WTF411
Version: next
OS: Windows 11

Re: Spoken ATC

Postby rleibner » Thu Jun 22, 2017 9:26 pm

Thanks, Josh. And I have some improvements in mind:
    * Instead "contact tower" Gnd and App should say "contact tower at <twr.freq>".
    * When available, on initial contact should suggest "ATIS available at <atis.freq>".
    * App should say "join <right/left> pattern on <downwind/base> and contact tower...".
But I still do not know how to solve these challenges :roll:
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby Octal450 » Fri Jun 23, 2017 12:27 am

Good idea,

I am not sure how to do this at this at the moment, you would need to get the frequencies and etc somewhere.

Kind Regards,
Josh
Skillset: JSBsim Flight Dynamics, Systems, Canvas, Autoflight/Control, Instrumentation, Animations
Aircraft: A320-family, MD-11, MD-80, Contribs in a few others

Octal450's GitHub|Launcher Catalog
|Airbus Dev Discord|Octal450 Hangar Dev Discord
User avatar
Octal450
 
Posts: 5583
Joined: Tue Oct 06, 2015 1:51 pm
Location: Huntsville, AL
Callsign: WTF411
Version: next
OS: Windows 11

Re: Spoken ATC

Postby rleibner » Fri Jun 23, 2017 3:20 am

Summarizing:

TODO list:
    #1. Insert some pauses in long answers (to improve TTS intelligibility ... for non-English speaking pilots like me :lol: )
    #2. Instead "contact tower" GND and APP should say "contact tower at <twr.freq>".
    #3. When available, on initial contact ATC should suggest "ATIS available at <atis.freq>".
    #4. APP should say "join <right/left> pattern on <downwind/base> and contact tower...".
Let's see ...
    #1. Maybe splitting the message in 2 or 3 sentences and using maketimer() ?
    #2. & #3. Donno how. Although airportinfo() gives many data (including the boolean has-metar), no freqs are there!
    #4. Can be done with some math and assuming standard patterns.
I think I'll start with the latter.
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby legoboyvdlp » Fri Jun 23, 2017 11:41 am

@rleibner,
I reccomend you submit this to the FlightGear developers mailing list if you want this included in FlightGear at some point. You just need to:
Go to: https://sourceforge.net/projects/flight ... gear-devel and fill in the form. You probably just need to put your email and country in and remove everything else.

Once you are subscribed, then, you just send an ordinary email to flightgear-devel AT lists.sourceforge DOT net
User avatar
legoboyvdlp
 
Posts: 7981
Joined: Sat Jul 26, 2014 2:28 am
Location: Northern Ireland
Callsign: G-LEGO
Version: next
OS: Windows 10 HP

Re: Spoken ATC

Postby rleibner » Sun Jun 25, 2017 9:13 pm

I certainly will, legoboydlp. I was just waiting to have version 0.2 debugged.
Which I copy:
Code: Select all
# **     atc.nas (save in $FG_ROOT/Nasal)    **
# **         v.: 0.2 beta                    **
# *********************************************
# ** by Rodolfo Leibner (rleibner@gmail.com) **
# ** Comments & enheacements are wellcome    **

 var speak = func() {
 
  # **** spell func. **************************
      var spell = func(str) {
      var s = split("",str);
      for(var i=0;i<size(s);i=i+1) {
        if(streq(s[i],"."))  s[i]="point" ;
      }
      return string.join(" ",s);
      };
     
  # **** abc func. *****************************
    var abc = func(str) {
    var alpha = {A:"alpha", B:"bravo", C:"charlie", D:"delta", E:"echo", F:"foxtrot",
                 G:"golf", H:"hotel", I:"india", J:"juliet", K:"kilo", L:"lima", M:"mike",
                 N:"november", O:"oscar", P:"papa",Q:"quebec", R:"romeo", S:"sierra",
                 T:"tango", U:"uniform", V:"victor", W:"whiskey", X:"X ray", Y:"yanki", Z:"zulu"};
    var s = split("",str);
    for(var i=0;i<size(s);i=i+1) {
      if(int(s[i])==nil)  s[i]=alpha[s[i]];   # Translate only letters
    };
    return string.join(" ",s);
    };
   
  # **** APPvoice func. *****************************
  var APPvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info.runways[best]); # from plane to apt
    #~ print(distance, " nm.");
    if(distance<10 or getprop("/position/altitude-agl-ft")<30) { return "You are inside CTR. Contact tower.";
    } else { "Turn to " ~spell(sprintf("%i",course)) ~" to join pattern runway " ~spell(best) ~" and contact tower.";
    } 
}
  # **** TWRvoice func. *****************************
  var TWRvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (crse, dist) = courseAndDistance(to); # from plane to rwy
    if(rwh-20<crse and rwh+20>crse and distance<10) {
       return "Runway " ~spell(best) ~", cleared to land, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots.";
    }
    var pttn =(streq(right(best, 1), "R"))? "Join right pattern runway " : "Join left pattern runway ";
    if(distance<10) {
       return pttn ~spell(best) ~", cleared to land, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots.";
    }
    return "Turn to " ~spell(sprintf("%i",course)) ~" to " ~pttn ~spell(best) ~" and report.";
    }
   
  # **** GNDvoice func. *****************************
  var GNDvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    if(distance>2 or getprop("/position/altitude-agl-ft")>30) { return "Contact to tower.";}
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (course, dist) = courseAndDistance(to); # from plane to rwy
    var type =(streq(station_type, "tower"))? "" : "tower ";
    if(dist* NM2M > 120) {   # Request for Taxi
      #~ print("dist=", dist*NM2M);
      return "Taxi to holding point Runway " ~spell(best) ~" and report " ~type ~"when ready.";
    } else {
      var awd =(streq(type,"") and (getprop("/position/altitude-agl-ft") < 30))?
        "Runway " ~spell(best) ~", cleared to takeoff, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots."
        : "Contact Tower.";
      return awd ;}
};
     
# *** Main ****************************************

# 1) Initialization
var a=call(func {
        return getprop("/instrumentation/comm/spk-atc");
    }, nil, nil, nil,);
if(a==nil) setprop("/instrumentation/comm/spk-atc", 0.0);

# 2) Check comm frequency
var cs = sprintf(getprop("/ai/models/aircraft/callsign"));
var icao = getprop("/instrumentation/comm/airport-id");   
var info = airportinfo(icao);
var qlty = getprop("/instrumentation/comm/signal-quality-norm");
var station_type = sprintf(getprop("/instrumentation/comm/station-type"));
var atc = string.replace(info.name,"Intl","International") ~" "~station_type ~". ";

if(atc==nil or qlty<0.01) { # if invalid freq or out of range
    gui.popupTip("Check comm freq.!",3);
    die("Check comm freq.");}

# 3) Get env. values
var q_ =  getprop("/environment/metar/pressure-sea-level-inhg");
var q =  spell(sprintf("%.2f",q_));
var qnh = sprintf("%d",q_/0.02953);
var wd = sprintf("%d",getprop("/environment/metar/base-wind-dir-deg"));
var wv = sprintf("%d",getprop("/environment/metar/base-wind-speed-kt"));
var rws = info.runways;

# 4) choose best rwy
var best = "";
var ang = 180.0;
foreach(var rw; keys(rws)){
  var a = abs(info.runways[rw].heading - getprop("/environment/metar/base-wind-dir-deg"));
  if(a<ang) {
     ang = a;
     best = rw;}
  }

# 5) Check current position
var pos_v = (getprop("/position/altitude-agl-ft") < 30)? "onground": "flying";
var to = {lat: info.runways[rw].lat, lon: info.runways[rw].lon};
var (course, dist) = courseAndDistance(to);
var pos_h = ((dist * NM2M) < 100)? "onrwy": "far";

# 6) Choose pertinent instruction
var QNH = ". QNH " ~qnh ~" or " ~q ~" inches. " ;
var tunned = getprop("/instrumentation/comm/frequencies/selected-mhz");
var voice = (getprop("/instrumentation/comm/spk-atc")==tunned)? abc(cs) ~". " : abc(cs)~ ". This is " ~atc  ~QNH ;

# ********** 4 debug only **********
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (crse, dist) = courseAndDistance(to); # from plane to rwy
    print("To apt: ",distance,"nm ", course,"deg.  To rwy: ",dist,"nm ", crse-rwh,"deg.");
    print("Alt agl: ", sprintf(getprop("/position/altitude-agl-ft")), "ft");
# ****************************************************

   
if(string.match(station_type, "approach*")) { voice ~= APPvoice(info, best);
} elsif ((streq(station_type, "tower")) and (getprop("/position/altitude-agl-ft") > 30)) {  # flying => Request for landing.
       voice ~= TWRvoice(info, best);
  } else {
  voice ~= GNDvoice(info, best);
  }

# 7) Speach instruction
print(voice); # 4 debug
#~ gui.popupTip(voice,15);
setprop("/sim/sound/voices/atc", voice);
setprop("/instrumentation/comm/spk-atc", tunned);
};

I was also waiting for someone to beta tester.
Regards
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby Hooray » Sun Jul 02, 2017 2:57 pm

Ideally, something like this would be turned into a so called "Nasal submodule" or an optional "addon":

http://wiki.flightgear.org/Howto:Create ... sal_module

That way, it would be easier to enable/disable, but also to update the whole thing.

Besides, you may want to consider encapsulating the way you are using aircraft-specific properties, so that this may eventually be used to interact with other (non-main) aircraft, such as for instance scripted AI traffic (think tanker.nas, bombable.nas etc)

The whole phraseology thing could also benefit from being encapsulated by using a hash with printf-style format strings, so that this could be loaded from a separate file, either using io.load_nasa() and/or some XML-configurable markup, so that non-coders could help grow the library of supported phraseology - as a matter of fact, there is quite a bit of machine-readable phraseology available on line in the form of public-domain sources (think FAA materials) that could be converted in a scripted fashion, so that the underlying script would not need to be changed.

It would require roughly 10-15 lines of Nasal code to come up with a PropertyList-based XML format to implement something like that using the helpers available in io.nas: http://wiki.flightgear.org/Nasal_librar ... dxml.28.29

In the mid-term, one of the more natural features to integrate this with would be the integrated tutorial system, which also happens to be implemented in scripting space using Nasal: http://wiki.flightgear.org/Tutorials

No matter how you intended to proceed with this, my suggestion would be to create a wiki article to document your work, maintain a list of ideas/feature requests (or even just bugs/issues), so that other contributors can get involved more easily - that would also be the right place to add tutorials on using/extending your script, and it could be linked to from the newsletter/changelog, too.
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Spoken ATC

Postby rleibner » Sun Jul 02, 2017 6:12 pm

Hooray wrote in Sun Jul 02, 2017 2:57 pm:Ideally, something like this would be turned into a so called "Nasal submodule" or an optional "addon":
That way, it would be easier to enable/disable, but also to update the whole thing.

I accept your suggestion and start working on it.
Besides, you may want to consider encapsulating the way you are using aircraft-specific properties, so that this may eventually be used to interact with other (non-main) aircraft, such as for instance scripted AI traffic (think tanker.nas, bombable.nas etc)

What do you mean by "encapsulating"? I have not wanted to create many specific properties to not unnecessarily overload the property tree.
Besides, how do I know which properties are aircraft-specific and which are not?
The whole phraseology thing could also benefit from being encapsulated by using a hash with printf-style format strings, so that this could be loaded from a separate file, either using io.load_nasa() and/or some XML-configurable markup,

OK, I'll study how to implement it.
In the mid-term, one of the more natural features to integrate this with would be the integrated tutorial system,

What do you mean, make the feature work similar to how the tutorials do it? Or create a new tutorial about Spoken-ATC communications?
No matter how you intended to proceed with this, my suggestion would be to create a wiki article to document your work
,
I will, if that helps to involve more people in the project.
Apart from the structure subject (of what we are talking about here), I have not received feedback on the behavior of version 1.0. And I need it, maybe the wiki gives us that.

Regards,
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby clm76 » Sun Jul 02, 2017 6:16 pm

Hi Rleibner,
Very good job, thanks.

I imported your code in the Citation X (patten hangar) and tried it.
A few comments :
- Problem with « die("Check comm freq."); » which gives a nasal error. I replaced it by a « return »
- The real callsign is in the property « sim/multiplay/callsign », so I used it with this code to remove the « - ».
Code: Select all
var cs = getprop("/sim/multiplay/callsign");
cs = string.replace(cs,"-","");

- With flightplans activated in the Route-manager or in a FMS (Flight Management System), the landing runway is pre-defined and the plane lands straight forward. I modified the code to take it into account.
Here the entire code modified :
Code: Select all
# **                 atc.nas                              **
# **         v.: 0.2 beta                    **
# *********************************************
# ** by Rodolfo Leibner (rleibner@gmail.com) **
# ** Comments & enheacements are welcome    **

var speak = func() {
 
  # **** spell func. **************************
   var spell = func(str) {
      var s = split("",str);
      for(var i=0;i<size(s);i=i+1) {
        if(streq(s[i],"."))  s[i]="point" ;
      }
      return string.join(" ",s);
  }
     
  # **** abc func. *****************************
  var abc = func(str) {
     var alpha = {A:"alpha", B:"bravo", C:"charlie", D:"delta", E:"echo", F:"foxtrot",
                 G:"golf", H:"hotel", I:"india", J:"juliet", K:"kilo", L:"lima", M:"mike",
                 N:"november", O:"oscar", P:"papa",Q:"quebec", R:"romeo", S:"sierra",
                 T:"tango", U:"uniform", V:"victor", W:"whiskey", X:"X ray", Y:"yanki", Z:"zulu"};
    var s = split("",str);
    for(var i=0;i<size(s);i=i+1) {
      if(int(s[i])==nil)  s[i]=alpha[s[i]];   # Translate only letters
    }
    return string.join(" ",s);
  }
   
  # **** APPvoice func. *****************************
  var APPvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info.runways[best]); # from plane to apt
    if(distance<10 or getprop("/position/altitude-agl-ft")<30) { return "You are inside CTR. Contact tower.";
    } else { "Turn to " ~spell(sprintf("%i",course)) ~" to join pattern runway " ~spell(best) ~" and contact tower.";
    }
   }

  # **** TWRvoice func. *****************************
  var TWRvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (crse, dist) = courseAndDistance(to); # from plane to rwy
    if(rwh-20<crse and rwh+20>crse and distance<=10) {
       return "Runway " ~spell(best) ~", cleared to land, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots.";
    }
    var pttn =(streq(right(best, 1), "R"))? "Join right pattern runway " : "Join left pattern runway ";
    if(distance<=10) {
       return pttn ~spell(best) ~", cleared to land, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots.";
    } else {
         if (rm) {
            return "Continue to "~spell(best)~" and report at "~spell("10")~" nautical miles from runway";
         } else {
            return "Turn to " ~spell(sprintf("%i",course)) ~" to " ~pttn ~spell(best) ~" and report.";
         }
      }
  }
   
  # **** GNDvoice func. *****************************
  var GNDvoice = func(info, best) {
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    if(distance>2 or getprop("/position/altitude-agl-ft")>30) { return "Contact to tower.";}
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (course, dist) = courseAndDistance(to); # from plane to rwy
    var type =(streq(station_type, "tower"))? "" : "tower ";
    if(dist* NM2M > 120) {   # Request for Taxi
      #~ print("dist=", dist*NM2M);
      return "Taxi to holding point Runway " ~spell(best) ~" and report " ~type ~"when ready.";
    } else {
      var awd =(streq(type,"") and (getprop("/position/altitude-agl-ft") < 30))?
        "Runway " ~spell(best) ~", cleared to takeoff, wind "~spell(wd) ~" degrees, " ~spell(wv) ~" knots."
        : "Contact Tower.";
      return awd;
      }
   }
     
# *** Main ****************************************

   # 1) Initialization
   var a=call(func {
    return getprop("/instrumentation/comm/spk-atc");
   }, nil, nil, nil,);
   if(a==nil) setprop("/instrumentation/comm/spk-atc", 0.0);

   # 2) Check comm frequency
   #var cs = sprintf(getprop("/ai/models/aircraft/callsign"));
   #var cs = right(getprop("/sim/multiplay/callsign"),4);
   var cs = getprop("/sim/multiplay/callsign");
   cs = string.replace(cs,"-","");
   var icao = getprop("/instrumentation/comm/airport-id");   
   var info = airportinfo(icao);
   var qlty = getprop("/instrumentation/comm/signal-quality-norm");
   var station_type = sprintf(getprop("/instrumentation/comm/station-type"));
   var atc = string.replace(info.name,"Intl","International") ~" "~station_type ~". ";

   if(atc==nil or qlty<0.01) { # if invalid freq or out of range
    gui.popupTip("Check comm freq.!",3);
   # die("Check comm freq.");
      return;
   }

   # 3) Get env. values
   var q_ =  getprop("/environment/metar/pressure-sea-level-inhg");
   var q =  spell(sprintf("%.2f",q_));
   var qnh = sprintf("%d",q_/0.02953);
   var wd = sprintf("%d",getprop("/environment/metar/base-wind-dir-deg"));
   var wv = sprintf("%d",getprop("/environment/metar/base-wind-speed-kt"));
   var rws = info.runways;

   # 4) choose best rwy
   var best = "";
   var ang = 180.0;
   var dest_rwy = nil;
   var rm = 0;
   if (getprop("/autopilot/route-manager/active")) {
      dest_rwy = getprop("/autopilot/route-manager/destination/runway");
      rm = 1; # flag
print("116 dest_rwy : ",dest_rwy);
   }

   foreach(var rw; keys(rws)){
print("120 : ",rw," - ",abs(info.runways[rw].heading));
      if (dest_rwy != nil and dest_rwy == rw) {
            best = rw;
print("123 best : ",best);
         break;
      } else {
      var a = abs(info.runways[rw].heading - getprop("/environment/metar/base-wind-dir-deg"));
      if(a<ang) {
         ang = a;
         best = rw;}
print("129 best : ",best);
      }
   }

   # 5) Check current position
   var pos_v = (getprop("/position/altitude-agl-ft") < 30)? "onground": "flying";
   var to = {lat: info.runways[rw].lat, lon: info.runways[rw].lon};
   var (course, dist) = courseAndDistance(to);
   var pos_h = ((dist * NM2M) < 100)? "onrwy": "far";

   # 6) Choose pertinent instruction
   var QNH = ". QNH " ~qnh ~" or " ~q ~" inches. " ;
   var tunned = getprop("/instrumentation/comm/frequencies/selected-mhz");
   var voice = (getprop("/instrumentation/comm/spk-atc")==tunned)? abc(cs) ~". " : abc(cs)~ ". This is " ~atc  ~QNH ;

# ********** 4 debug only **********
    var (course,distance)=courseAndDistance(info); # from plane to apt
    var rwh=info.runways[best].heading;
    var to = {lat: info.runways[best].lat, lon: info.runways[best].lon};
    var (crse, dist) = courseAndDistance(to); # from plane to rwy
# print("146 - To apt: ",distance,"nm ", course,"deg.  To rwy: ",dist,"nm ", crse-rwh,"deg.");
#    print("Alt agl: ", sprintf(getprop("/position/altitude-agl-ft")), "ft");
# ****************************************************

   
   if(string.match(station_type, "approach*")) { voice ~= APPvoice(info, best);
   } elsif ((streq(station_type, "tower")) and (getprop("/position/altitude-agl-ft") > 30)) {  # flying => Request for landing.
      voice ~= TWRvoice(info, best);
  } else {
      voice ~= GNDvoice(info, best);
  }

   # 7) Speach instruction
   print(voice); # 4 debug
   gui.popupTip(voice,15);
   setprop("/sim/sound/voices/atc", voice);
   setprop("/instrumentation/comm/spk-atc", tunned);
};

Also problems with comm frequencies when they are similar between two airports or approaches. Example : Funchal (LPMA - Madeira) and Almeria (LEAM -Spain) which have 118.35 as airport frequency. A flightplan from France to Madeira give LEAM for Airport-id. However, I think it’s a Flightgear problem, not an Atc problem.
clm76
 
Posts: 204
Joined: Tue Oct 30, 2012 9:18 pm
Location: France - LFOH
Callsign: F-GCLM
Version: 2020.4.0
OS: Linux Mint 20.2

Re: Spoken ATC

Postby Hooray » Sun Jul 02, 2017 6:49 pm

regarding die/return to do error handling, it would be better to set a corresponding script specific property and register a listener to deal with any changes, so that the corresponding message can be shown in the console (using print() ) or the GUI using a popup/tooltip. Besides, by agreeing on using the property tree for such purposes, even other UIs (think Phi) would theoretically be able to show relevant messages without any hassle
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Spoken ATC

Postby rleibner » Sun Jul 02, 2017 7:32 pm

Hi clm76,
Thanks, you are right.
I'm taking your 3 corrections and I include them in version 1.0 which you can download from here.
Into the zip, you'll found 2 nasal scripts and a Readme file with instructions.
You will notice many improvements compared to the previous version.
Although many things will change due to Hooray's contributions (I'm in the middle of work), this is what we have for now.

Regards,
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby rleibner » Sat Jul 08, 2017 5:00 pm

Hi,
Version 1.2 is available in the wiki page about this feature.

What is new in version 1.2:

    * Submodule structure: thus, proper installation and not setlistener troubles.
    * Separate phraseology file: enabling easier and cleaner customizations.
    * Some improvements in ATC behavior.

I know that there probably are some redundant or unnecessary code lines.
I count on your feedback to make it more efficient and more realistic.

Regards,
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Re: Spoken ATC

Postby Hooray » Sat Jul 08, 2017 7:12 pm

My suggestion would be to introduce fields for multiple languages into the hash, so that we can add strings with variables that are to be replaced using sprintf: http://wiki.flightgear.org/Nasal_library#sprintf.28.29
Please don't send support requests by PM, instead post your questions on the forum so that all users can contribute and benefit
Thanks & all the best,
Hooray
Help write next month's newsletter !
pui2canvas | MapStructure | Canvas Development | Programming resources
Hooray
 
Posts: 12707
Joined: Tue Mar 25, 2008 9:40 am
Pronouns: THOU

Re: Spoken ATC

Postby rleibner » Sat Jul 08, 2017 9:56 pm

Mmm .... I do not know if I understand you.
Multiple languages
Is that the TTS can be configured in other languages (other than English)? :shock:
to introduce fields into the hash, so that we can add strings with variables that are to be replaced using sprintf
I know the sprintf() func. to format strings, but cannot see what's the point here. Can you give me an example of such structure? :roll:
Rodolfo
*************************
Non-shared knowledge is lost knowledge
User avatar
rleibner
 
Posts: 269
Joined: Fri May 19, 2017 8:17 pm
Location: Uruguay - SUMU
Callsign: CX-BEX
Version: next
OS: Ubuntu 18.04.4

Next

Return to Nasal

Who is online

Users browsing this forum: No registered users and 3 guests