]> git.mxchange.org Git - flightgear.git/blob - src/ATC/ATCmgr.cxx
Changes to support voice rendering of ATC
[flightgear.git] / src / ATC / ATCmgr.cxx
1 // ATCmgr.cxx - Implementation of FGATCMgr - a global Flightgear ATC manager.
2 //
3 // Written by David Luff, started February 2002.
4 //
5 // Copyright (C) 2002  David C Luff - david.luff@nottingham.ac.uk
6 //
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
11 //
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 // General Public License for more details.
16 //
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20
21 //#include <Time/event.hxx>
22
23 #include <simgear/misc/sg_path.hxx>
24 #include <simgear/debug/logstream.hxx>
25
26 #include "ATCmgr.hxx"
27 #include "atislist.hxx"
28 //#include "groundlist.hxx"
29 #include "towerlist.hxx"
30 #include "approachlist.hxx"
31 #include "ATCdisplay.hxx"
32
33 /*
34 // periodic radio station search wrapper
35 static void fgATCSearch( void ) {
36         globals->get_ATC_mgr()->Search();
37 }
38 */ //This wouldn't compile - including Time/event.hxx breaks it :-(
39
40 FGATCMgr::FGATCMgr() {
41         comm1_ident = "";
42         comm1_atis_ident = "";
43         comm1_tower_ident = "";
44         comm1_approach_ident = "";
45         last_comm1_ident = "";
46         last_comm1_atis_ident = "";
47         last_comm1_tower_ident = "";
48         last_comm1_approach_ident = "";
49         approach_ident = "";
50         last_in_range = false;
51         comm1_atis_valid = false;
52         comm1_tower_valid = false;
53         comm1_approach_valid = false;
54 }
55
56 FGATCMgr::~FGATCMgr() {
57 }
58
59 void FGATCMgr::bind() {
60 }
61
62 void FGATCMgr::unbind() {
63 }
64
65 void FGATCMgr::init() {
66         comm1_node = fgGetNode("/radios/comm[0]/frequencies/selected-mhz", true);
67         comm2_node = fgGetNode("/radios/comm[1]/frequencies/selected-mhz", true);
68         lon_node = fgGetNode("/position/longitude-deg", true);
69         lat_node = fgGetNode("/position/latitude-deg", true);
70         elev_node = fgGetNode("/position/altitude-ft", true);
71         atc_list_itr = atc_list.begin();
72         // Search for connected ATC stations once per 0.8 seconds or so
73         // global_events.Register( "fgATCSearch()", fgATCSearch,
74         //                  fgEVENT::FG_EVENT_READY, 800);
75         // For some reason the above doesn't compile - including Time/event.hxx stops compilation.
76         
77         // Initialise the airport_atc_map - we'll cheat for now and just hardcode KEMT and any others that may be needed for development
78         AirportATC *a = new AirportATC;
79         a->lon = -118.034719;
80         a->lat = 34.086114;
81         a->elev = 296.0;
82         a->atis_freq = 118.75;
83         a->atis_active = false;
84         a->tower_freq = 121.2;
85         a->tower_active = false;
86         a->ground_freq = 125.9;
87         a->ground_active = false;
88         
89         //a->set_by_AI = true;
90         //a->set_by_comm_search = false;
91         
92         airport_atc_map[(string)"KEMT"] = a;
93
94 #ifdef ENABLE_AUDIO_SUPPORT     
95         // Load all available voices.
96         // For now we'll do one hardwired one
97         voiceOK = v1.LoadVoice("default");
98         /* I've loaded the voice even if /sim/sound/audible is false
99         *  since I know no way of forcing load of the voice if the user
100         *  subsequently switches /sim/sound/audible to true. */
101 #else
102         voice = false;
103 #endif
104 }
105
106 void FGATCMgr::update(double dt) {
107         //Traverse the list of active stations.
108         //Only update one class per update step to avoid the whole ATC system having to calculate between frames.
109         //Eventually we should only update every so many steps.
110         //cout << "In FGATCMgr::update - atc_list.size = " << atc_list.size() << '\n';
111         if(atc_list.size()) {
112                 if(atc_list_itr == atc_list.end()) {
113                         atc_list_itr = atc_list.begin();
114                 }
115                 (*atc_list_itr)->Update();
116                 ++atc_list_itr;
117         }
118         
119         // Search the tuned frequencies every now and then - this should be done with the event scheduler
120         static int i = 0;
121         if(i == 30) {
122                 Search();
123                 i = 0;
124         }
125         ++i;
126 }
127 /*
128 // Remove from list only if not needed by the AI system
129 void FGATCMgr::CommRemoveFromList(const char* id, atc_type tp) {
130         AirportATC a;
131         if(GetAirportATCDetails((string)id, &a)) {
132                 if(a.set_by_AI) {
133                         // Don't remove
134                         a.set_by_comm_search = false;
135                         airport_atc_map[(string)id] = a;
136                         return;
137                 } else {
138                         // remove
139                 }
140         }
141 }
142 */    
143
144 // Remove from list - should only be called from above or similar
145 void FGATCMgr::RemoveFromList(const char* id, atc_type tp) {
146         //cout << "Requested type = " << tp << '\n';
147         //cout << "id = " << id << '\n';
148         atc_list_itr = atc_list.begin();
149         while(atc_list_itr != atc_list.end()) {
150                 //cout << "type = " << (*atc_list_itr)->GetType() << '\n';
151                 //cout << "Ident = " << (*atc_list_itr)->GetIdent() << '\n';
152                 if( (!strcmp((*atc_list_itr)->GetIdent(), id))
153                         && ((*atc_list_itr)->GetType() == tp) ) {
154                                 //Before removing it stop it transmitting!!
155                                 //cout << "OBLITERATING FROM LIST!!!\n";
156                                 (*atc_list_itr)->SetNoDisplay();
157                                 (*atc_list_itr)->Update();
158                                 delete (*atc_list_itr);
159                                 atc_list_itr = atc_list.erase(atc_list_itr);
160                                 break;
161                         }  // Note that that can upset where we are in the list but that doesn't really matter
162                 ++atc_list_itr;
163         }
164 }
165
166
167 //DCL - this routine untested so far.
168 // Find in list - return a currently active ATC pointer given ICAO code and type
169 FGATC* FGATCMgr::FindInList(const char* id, atc_type tp) {
170         atc_list_itr = atc_list.begin();
171         while(atc_list_itr != atc_list.end()) {
172                 if( (!strcmp((*atc_list_itr)->GetIdent(), id))
173                         && ((*atc_list_itr)->GetType() == tp) ) {
174                                 return(*atc_list_itr);
175                         }       // Note that that can upset where we are in the list but that shouldn't really matter
176                 ++atc_list_itr;
177         }
178         // We need a fallback position
179         SG_LOG(SG_GENERAL, SG_ALERT, "*** Failed to find FGATC* in FGATCMgr::FindInList - this should not happen!");
180         return(NULL);
181 }
182
183 // Returns true if the airport is found in the map
184 bool FGATCMgr::GetAirportATCDetails(string icao, AirportATC* a) {
185         if(airport_atc_map.find(icao) != airport_atc_map.end()) {
186                 *a = *airport_atc_map[icao];
187                 return(true);
188         } else {
189                 return(false);
190         }
191 }
192
193
194 // Return a pointer to a given sort of ATC at a given airport and activate if necessary
195 // ONLY CALL THIS FUNCTION AFTER FIRST CHECKING THE SERVICE EXISTS BY CALLING GetAirportATCDetails
196 // FIXME - we really ought to take out the necessity for two function calls by simply returning
197 // a NULL pointer if the service doesn't exist and requiring the caller to check for it (NULL).
198 FGATC* FGATCMgr::GetATCPointer(string icao, atc_type type) {
199         AirportATC *a = airport_atc_map[icao];
200         //cout << "a->lon = " << a->lon << '\n';
201         //cout << "a->elev = " << a->elev << '\n';
202         //cout << "a->tower_freq = " << a->tower_freq << '\n';
203         switch(type) {
204                 case TOWER:
205                 if(a->tower_active) {
206                         // Get the pointer from the list
207                         return(FindInList(icao.c_str(), type)); // DCL - this untested so far.
208                 } else {
209                         FGTower* t = new FGTower;
210                         if(current_towerlist->query(a->lon, a->lat, a->elev, a->tower_freq, &tower)) {
211                                 *t = tower;
212                                 atc_list.push_back(t);
213                                 a->tower_active = true;
214                                 airport_atc_map[icao] = a;
215                                 return(t);
216                         } else {
217                                 cout << "ERROR - tower that should exist in FGATCMgr::GetATCPointer for airport " << icao << " not found\n";
218                         }
219                 }
220                 break;
221                 // Lets add the rest to get rid of the compiler warnings even though we don't need them yet.
222                 case APPROACH:
223                 break;
224                 case ATIS:
225                 SG_LOG(SG_GENERAL, SG_ALERT, "ERROR - ATIS station should not be requested from FGATCMgr::GetATCPointer");
226                 break;
227                 case GROUND:
228                 break;
229                 case INVALID:
230                 break;
231                 case ENROUTE:
232                 break;
233                 case DEPARTURE:
234                 break;
235         }
236         
237         SG_LOG(SG_GENERAL, SG_ALERT, "ERROR IN FGATCMgr - reached end of GetATCPointer");
238         
239         return(NULL);
240 }
241
242
243 // Render a transmission
244 // Outputs the transmission either on screen or as audio depending on user preference
245 // The repeating flag indicates whether the message should be repeated continuously or played once.
246 void FGATCMgr::Render(string msg, bool repeating) {
247 #ifdef ENABLE_AUDIO_SUPPORT
248         voice = voiceOK && fgGetBool("/sim/sound/audible");
249         if(voice) {
250                 int len;
251                 unsigned char* buf = v1.WriteMessage((char*)msg.c_str(), len, voice);
252                 if(voice) {
253                         FGSimpleSound* simple = new FGSimpleSound(buf, len);
254                         simple->set_volume(2.0);
255                         globals->get_soundmgr()->add(simple, refname);
256                         if(repeating) {
257                                 globals->get_soundmgr()->play_looped(refname);
258                         } else {
259                                 globals->get_soundmgr()->play_once(refname);
260                         }
261                 }
262                 delete[] buf;
263         }
264 #endif  // ENABLE_AUDIO_SUPPORT
265         if(!voice) {
266                 // first rip the underscores and the pause hints out of the string - these are for the convienience of the voice parser
267                 for(unsigned int i = 0; i < msg.length(); ++i) {
268                         if((msg.substr(i,1) == "_") || (msg.substr(i,1) == "/")) {
269                                 msg[i] = ' ';
270                         }
271                 }
272                 globals->get_ATC_display()->RegisterRepeatingMessage(msg);
273         }
274         playing = true; 
275 }
276
277
278 // Cease rendering a transmission.
279 // At the moment this can handle one transmission active at a time only.
280 void FGATCMgr::NoRender() {
281         if(playing) {
282                 if(voice) {
283 #ifdef ENABLE_AUDIO_SUPPORT             
284                         globals->get_soundmgr()->stop(refname);
285                         globals->get_soundmgr()->remove(refname);
286 #endif
287                 } else {
288                         globals->get_ATC_display()->CancelRepeatingMessage();
289                 }
290                 playing = false;
291         }
292 }
293
294 void FGATCMgr::Search() {
295         
296         ////////////////////////////////////////////////////////////////////////
297         // Comm1.
298         ////////////////////////////////////////////////////////////////////////
299         //cout << "In FGATCMgr::Search() - atc_list.size = " << atc_list.size() << '\n';
300         
301         comm1_freq = comm1_node->getDoubleValue();
302         //cout << "************* comm1_freq = " << comm1_freq << '\n';
303         double lon = lon_node->getDoubleValue();
304         double lat = lat_node->getDoubleValue();
305         double elev = elev_node->getDoubleValue() * SG_FEET_TO_METER;
306         
307         // Store the comm1_type
308         //atc_type old_comm1_type = comm1_type;
309         
310         // We must be able to generalise some of the repetetive searching below!
311         
312         //Search for ATIS first
313         if(current_atislist->query(lon, lat, elev, comm1_freq, &atis)) {
314                 //cout << "atis found in radiostack search !!!!" << endl;
315                 //cout << "last_comm1_atis_ident = " << last_comm1_atis_ident << '\n';
316                 //cout << "comm1_type " << comm1_type << '\n';
317                 comm1_atis_ident = atis.GetIdent();
318                 comm1_atis_valid = true;
319                 if(last_comm1_atis_ident != comm1_atis_ident) {
320                         if(last_comm1_atis_ident != "") {
321                                 RemoveFromList(last_comm1_atis_ident, ATIS);
322                         }
323                         last_comm1_atis_ident = comm1_atis_ident;
324                         //cout << "last_comm1_atis_ident = " << last_comm1_atis_ident << '\n';
325                         comm1_type = ATIS;
326                         comm1_elev = atis.get_elev();
327                         comm1_range = FG_ATIS_DEFAULT_RANGE;
328                         comm1_effective_range = comm1_range;
329                         comm1_x = atis.get_x();
330                         comm1_y = atis.get_y();
331                         comm1_z = atis.get_z();
332                         FGATIS* a = new FGATIS;
333                         *a = atis;
334                         a->SetDisplay();
335                         atc_list.push_back(a);
336                         //cout << "Found a new atis station in range" << endl;
337                         //cout << " id = " << atis.GetIdent() << endl;
338                         return;  //This rather assumes that we never have more than one type of station in range.
339                 }
340         } else {
341                 if(comm1_atis_valid) {
342                         //cout << "Removing ATIS " << comm1_atis_ident << " from list\n";
343                         RemoveFromList(comm1_atis_ident, ATIS);
344                         comm1_atis_valid = false;
345                         if(comm1_type == ATIS) {
346                                 comm1_type = INVALID;
347                         }
348                         comm1_atis_ident = "";
349                         //comm1_trans_ident = "";
350                         last_comm1_atis_ident = "";
351                 }
352                 //cout << "not picking up atis" << endl;
353         }
354         
355         //Next search for tower
356         //cout << "comm1_freq = " << comm1_freq << '\n';
357         if(current_towerlist->query(lon, lat, elev, comm1_freq, &tower)) {
358                 //cout << "tower found in radiostack search !!!!" << endl;
359                 comm1_tower_ident = tower.GetIdent();
360                 //cout << "comm1_tower_ident = " << comm1_tower_ident << '\n';
361                 comm1_tower_valid = true;
362                 if(last_comm1_tower_ident != comm1_tower_ident) {
363                         if(last_comm1_tower_ident != "") {
364                                 RemoveFromList(last_comm1_tower_ident, TOWER);
365                         }
366                         last_comm1_tower_ident = comm1_tower_ident;
367                         comm1_type = TOWER;
368                         comm1_elev = tower.get_elev();
369                         comm1_range = FG_TOWER_DEFAULT_RANGE;
370                         comm1_effective_range = comm1_range;
371                         comm1_x = tower.get_x();
372                         comm1_y = tower.get_y();
373                         comm1_z = tower.get_z();
374                         FGTower* t = new FGTower;
375                         *t = tower;
376                         t->SetDisplay();
377                         atc_list.push_back(t);
378                         //cout << "Found a new tower station in range" << endl;
379                         //cout << " id = " << tower.GetIdent() << endl;
380                         return;  //This rather assumes that we never have more than one type of station in range.
381                 }
382         } else {
383                 if(comm1_tower_valid) {
384                         //cout << "removing tower\n";
385                         RemoveFromList(comm1_tower_ident, TOWER);
386                         //comm1_valid = false;
387                         if(comm1_type == TOWER) {
388                                 comm1_type = INVALID;   // Only invalidate if we haven't switched it to something else
389                         }
390                         comm1_tower_valid = false;
391                         comm1_tower_ident = "";
392                         last_comm1_tower_ident = "";
393                         //comm1_ident = "";
394                         //comm1_trans_ident = "";
395                         //last_comm1_ident = "";
396                 }
397                 //cout << "not picking up tower" << endl;
398         }
399         /*
400         //Next search for Ground control
401         if(current_groundlist->query(lon, lat, elev, comm1_freq, &ground)) {
402                 //cout << "Ground Control found in radiostack search !!!!" << endl;
403                 comm1_ident = ground.GetIdent();
404                 comm1_valid = true;
405                 if((last_comm1_ident != comm1_ident) || (comm1_type != GROUND)) {
406                         if(last_comm1_ident != "") {
407                                 RemoveFromList(last_comm1_ident, GROUND);
408                         }
409                         last_comm1_ident = comm1_ident;
410                         comm1_type = GROUND;
411                         comm1_elev = ground.get_elev();
412                         comm1_range = FG_GROUND_DEFAULT_RANGE;
413                         comm1_effective_range = comm1_range;
414                         comm1_x = ground.get_x();
415                         comm1_y = ground.get_y();
416                         comm1_z = ground.get_z();
417                         FGGround* g = new FGGround;
418                         *g = ground;
419                         g->SetDisplay();
420                         atc_list.push_back(g);
421                         // For now we will automatically make contact with ground when the radio is tuned.
422                         // This rather assumes that the user tunes the radio at the appropriate place
423                         // (ie. having just turned off the runway) and only uses ground control on arrival
424                         // but its a start!
425                         g->NewArrival(current_plane);
426                         //cout << "Found a new ground station in range" << endl;
427                         //cout << " id = " << ground.GetIdent() << endl;
428                         return;  //This rather assumes that we never have more than one type of station in range.
429                 }
430         } else {
431                 if((comm1_valid) && (comm1_type == GROUND)) {
432                         RemoveFromList(comm1_ident, GROUND);
433                         comm1_valid = false;
434                         comm1_type = INVALID;
435                         comm1_ident = "";
436                         //comm1_trans_ident = "";
437                         last_comm1_ident = "";
438                 }
439                 //cout << "not picking up ground control" << endl;
440         }
441         */
442         // ================================================================================
443         // Search for Approach stations
444         // ================================================================================
445         // init number of approach stations reachable by plane
446         int  num_app = 0;
447         
448         // search stations in range
449         current_approachlist->query_bck(lon, lat, elev, approaches, max_app, num_app);
450         if (num_app != 0) {
451                 //cout << num_app << " approaches found in radiostack search !!!!" << endl;
452                 
453                 for ( int i=0; i<num_app; i++ ) {
454                         bool new_app = true;
455                         approach_ident = approaches[i].GetIdent();
456                         
457                         // check if station already exists on ATC stack
458                         atc_list_itr = atc_list.begin();
459                         while(atc_list_itr != atc_list.end()) {
460                                 //cout << "ATC list: " << (*atc_list_itr)->GetIdent() << endl;
461                                 if((!strcmp((*atc_list_itr)->GetIdent(), approach_ident))
462                                         && ((*atc_list_itr)->GetType() == APPROACH) ) {
463                                                 new_app = false;
464                                                 string pid = "Player";
465                                                 (*atc_list_itr)->AddPlane(pid);
466                                                 (*atc_list_itr)->Update();
467                                                 break;
468                                         }
469                                 ++atc_list_itr;
470                         }
471                         // generate new Approach on ATC stack
472                         if (new_app) {
473                                 FGApproach* a = new FGApproach;
474                                 *a = approaches[i];
475                                 string pid = "Player";
476                                 a->AddPlane(pid);
477                                 a->Update();
478                                 a->SetDisplay();
479                                 atc_list.push_back(a);
480                                 //cout << "Found a new approach station in range: Id = " 
481                                 //     << approaches[i].GetIdent() << endl;
482                         }
483                 }
484         }
485         
486         // remove planes which are out of range
487         atc_list_itr = atc_list.begin();
488         while(atc_list_itr != atc_list.end()) {
489                 if((*atc_list_itr)->GetType() == APPROACH ) {
490                         int np = (*atc_list_itr)->RemovePlane();
491                         // if approach has no planes left remove it from ATC list
492                         if ( np == 0) {
493                                 (*atc_list_itr)->SetNoDisplay();
494                                 (*atc_list_itr)->Update();
495                                 delete (*atc_list_itr);
496                                 atc_list_itr = atc_list.erase(atc_list_itr);
497                                 break;     // the other stations will be checked next time
498                         }
499                 }
500                 ++atc_list_itr;
501         }
502 }