FlightGear next
marker_beacon.cxx
Go to the documentation of this file.
1// marker_beacon.cxx -- class to manage the marker beacons
2//
3// Written by Curtis Olson, started April 2000.
4//
5// Copyright (C) 2000 Curtis L. Olson - http://www.flightgear.org/~curt
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20//
21// $Id$
22
23
24#include <config.h>
25
26#include <stdio.h> // snprintf
27
28#include <simgear/compiler.h>
29#include <simgear/math/sg_random.hxx>
30#include <simgear/misc/sg_path.hxx>
31#include <simgear/sound/sample_group.hxx>
32
33#include <Main/fg_props.hxx>
34#include <Navaids/navlist.hxx>
35
36#include "marker_beacon.hxx"
37#include <Sound/beacon.hxx>
38
39#include <string>
40
41using std::string;
42
43
45{
46 switch (ty) {
48 return FGBeacon::instance()->get_inner();
52 return FGBeacon::instance()->get_outer();
53 default:
54 return nullptr;
55 }
56}
57
59{
60 switch (ty) {
61 case FGMarkerBeacon::INNER: return "inner-marker";
62 case FGMarkerBeacon::MIDDLE: return "middle-marker";
63 case FGMarkerBeacon::OUTER: return "outer-marker";
64 default:
65 return {};
66 }
67}
68
69// Constructor
70FGMarkerBeacon::FGMarkerBeacon(SGPropertyNode *node) :
71 _time_before_search_sec(0.0)
72{
73 // backwards-compatability supply path
74 setDefaultPowerSupplyPath("/systems/electrical/outputs/nav[0]");
75 readConfig(node, "marker-beacon");
76
77 string blinkMode = node->getStringValue("blink-mode");
78 if (blinkMode == "standard") {
79 _blinkMode = BlinkMode::Standard;
80 } else if (blinkMode == "continuous") {
81 _blinkMode = BlinkMode::Continuous;
82 }
83}
84
85
86// Destructor
90
91
92void
94{
95 SGPropertyNode *node = fgGetNode(nodePath(), true );
97
98 // Inputs
99 sound_working = fgGetNode("/sim/sound/working", true);
100 audio_btn = node->getChild("audio-btn", 0, true);
101 audio_vol = node->getChild("volume", 0, true);
102
103 if (audio_btn->getType() == simgear::props::NONE)
104 audio_btn->setBoolValue( true );
105
106 auto smgr = globals->get_subsystem<SGSoundMgr>();
107 if (smgr) {
108 _audioSampleGroup = smgr->find("avionics", true);
109 _audioSampleGroup->tie_to_listener();
110
111 sound_working->addChangeListener(this);
112 audio_btn->addChangeListener(this);
113 audio_vol->addChangeListener(this);
114 }
115
116 reinit();
117}
118
119void
121{
122 _time_before_search_sec = 0.0;
123 _lastBeacon = NOBEACON;
124 updateOutputProperties(false);
125}
126
127void
129{
130 string branch = nodePath();
131
132 _innerBlinkNode = fgGetNode(branch + "/inner", true);
133 _middleBlinkNode = fgGetNode(branch + "/middle", true);
134 _outerBlinkNode = fgGetNode(branch + "/outer", true);
135}
136
137
138void
140{
141 string branch = nodePath();
142
143 fgUntie((branch + "/inner").c_str());
144 fgUntie((branch + "/middle").c_str());
145 fgUntie((branch + "/outer").c_str());
146
147 if (_audioSampleGroup) {
148 sound_working->removeChangeListener(this);
149 audio_btn->removeChangeListener(this);
150 audio_vol->removeChangeListener(this);
151 }
152
154}
155
156// Update the various nav values based on position and valid tuned in navs
157void
159{
161 _lastBeacon = NOBEACON;
162 stopAudio();
163 updateOutputProperties(false);
164 return;
165 }
166
167 _time_before_search_sec -= dt;
168 if ( _time_before_search_sec < 0 ) {
169 search();
170 }
171
172 if (_audioPropertiesChanged) {
173 updateAudio();
174 }
175
176 if (_lastBeacon != NOBEACON) {
177 // compute blink to match audio
178 // we use our own timing here (instead of dt) since audio rate is not affected
179 // by pause or time acceleration, so this should stay in sync.
180 const int elapasedUSec = (SGTimeStamp::now() - _audioStartTime).toUSecs();
181
182 bool on = true;
183 if (_blinkMode != BlinkMode::Continuous) {
184 int t = elapasedUSec % _beaconTiming.durationUSec;
185 for (int i = 0; i < 4; i++) {
186 t -= _beaconTiming.periodsUSec.at(i);
187 if (t < 0) {
188 // if value is negative, current time is within this
189 // period, so we are finished.
190 break;
191 }
192
193 // each period, the sense flips
194 on = !on;
195 } // of periods iteration
196 }
197
198 updateOutputProperties(on);
199 }
200}
201
202static void lazyChangeBoolProp(SGPropertyNode* node, bool v)
203{
204 if (node->getBoolValue() != v) {
205 node->setBoolValue(v);
206 }
207}
208
209void FGMarkerBeacon::updateOutputProperties(bool on)
210{
211 // map our beacon nodes to indices which correspond to the fgMkrBeacType enum
212 // this allows to use '_lastBeacon' to select whhich index should be on
213 // we set all other ones to off to ensure consistency in weird cases, eg
214 // going from one beaon type to another in a single update.
215 SGPropertyNode* beacons[4] = {nullptr, _innerBlinkNode.get(), _middleBlinkNode.get(), _outerBlinkNode.get()};
216 for (int b = INNER; b <= OUTER; b++) {
217 const bool bOn = on && (_lastBeacon == b);
218 lazyChangeBoolProp(beacons[b], bOn);
219 }
220}
221
222void FGMarkerBeacon::updateAudio()
223{
224 _audioPropertiesChanged = false;
225 if (!_audioSampleGroup)
226 return;
227
228 float volume = audio_vol->getFloatValue();
229 if (!audio_btn->getBoolValue()) {
230 // mute rather than stop, so we don't lose sync with the visual blink
231 volume = 0.0;
232 }
233
234 SGSoundSample* mkr = _audioSampleGroup->find(sampleNameForBeacon(_lastBeacon));
235 if (mkr) {
236 mkr->set_volume(volume);
237 }
238}
239
240
241static bool check_beacon_range( const SGGeod& pos,
242 FGPositioned *b )
243{
244 double d = distSqr(b->cart(), SGVec3d::fromGeod(pos));
245 // cout << " distance = " << d << " ("
246 // << FG_ILS_DEFAULT_RANGE * SG_NM_TO_METER
247 // * FG_ILS_DEFAULT_RANGE * SG_NM_TO_METER
248 // << ")" << endl;
249
250 //std::cout << " range = " << sqrt(d) << std::endl;
251
252 // cout << "elev = " << elev * SG_METER_TO_FEET
253 // << " current->get_elev() = " << current->get_elev() << endl;
254 double elev_ft = pos.getElevationFt();
255 double delev = elev_ft - b->elevation();
256
257 // max range is the area under r = 2.4 * alt or r^2 = 4000^2 - alt^2
258 // whichever is smaller. The intersection point is 1538 ...
259 double maxrange2; // feet^2
260 if ( delev < 1538.0 ) {
261 maxrange2 = 2.4 * 2.4 * delev * delev;
262 } else if ( delev < 4000.0 ) {
263 maxrange2 = 4000 * 4000 - delev * delev;
264 } else {
265 maxrange2 = 0.0;
266 }
267 maxrange2 *= SG_FEET_TO_METER * SG_FEET_TO_METER; // convert to meter^2
268 //std::cout << "delev = " << delev << " maxrange = " << sqrt(maxrange2) << std::endl;
269
270 // match up to twice the published range so we can model
271 // reduced signal strength
272 if ( d < maxrange2 ) {
273 return true;
274 } else {
275 return false;
276 }
277}
278
280{
281public:
283 {
284 return FGPositioned::OM;
285 }
286
288 {
289 return FGPositioned::IM;
290 }
291};
292
293// Update current nav/adf radio stations based on current postition
295{
296 // reset search time
297 _time_before_search_sec = 0.5;
298
299 const SGGeod pos = globals->get_aircraft_position();
300 if (!pos.isValid()) {
301 // avoid error flood when core positions goes wrong
302 return;
303 }
304
305 // get closest marker beacon - within a 1nm cutoff
306 BeaconFilter filter;
307 FGPositionedRef b = FGPositioned::findClosest(pos, 1.0, &filter);
308
309 fgMkrBeacType beacon_type = NOBEACON;
310 bool inrange = false;
311 if ( b != NULL ) {
312 if ( b->type() == FGPositioned::OM ) {
313 beacon_type = OUTER;
314 } else if ( b->type() == FGPositioned::MM ) {
315 beacon_type = MIDDLE;
316 } else if ( b->type() == FGPositioned::IM ) {
317 beacon_type = INNER;
318 }
319 inrange = check_beacon_range( pos, b.ptr() );
320 }
321
322 if ( b == NULL || !inrange || !isServiceableAndPowered())
323 {
324 beacon_type = NOBEACON;
325 }
326
327 changeBeaconType(beacon_type);
328}
329
330void FGMarkerBeacon::changeBeaconType(fgMkrBeacType newType)
331{
332 if (newType == _lastBeacon)
333 return;
334
335 _lastBeacon = newType;
336 stopAudio(); // stop any existing playback
337
338 if (newType == NOBEACON) {
339 updateOutputProperties(false);
340 return;
341 }
342
343 if (_blinkMode == BlinkMode::Standard) {
344 // get correct timings from the sounds generator
345 switch (newType) {
346 case INNER:
347 _beaconTiming = FGBeacon::instance()->getTimingForInner();
348 break;
349 case MIDDLE:
350 _beaconTiming = FGBeacon::instance()->getTimingForMiddle();
351 break;
352 case OUTER:
353 _beaconTiming = FGBeacon::instance()->getTimingForOuter();
354 break;
355 default:
356 break;
357 }
358 } else if (_blinkMode == BlinkMode::BackwardsCompatible) {
359 // older FG versions used same timing for alll beacon types :(
360 _beaconTiming = FGBeacon::BeaconTiming{};
361 _beaconTiming.durationUSec = 500000;
362 _beaconTiming.periodsUSec[0] = 400000;
363 _beaconTiming.periodsUSec[1] = 100000;
364 }
365
366
367 if (_audioSampleGroup) {
368 // create sample as required
369 const auto name = sampleNameForBeacon(newType);
370 if (!_audioSampleGroup->exists(name)) {
371 SGSoundSample* sound = createSampleForBeacon(newType);
372 if (sound) {
373 _audioSampleGroup->add(sound, name);
374 }
375 }
376
377 _audioSampleGroup->play_looped(name);
378 updateAudio(); // sync volume+mute now
379 }
380
381 // we use this timing for visuals as well, so do this even if we have
382 // no audio sample group
383 _audioStartTime.stamp();
384}
385
386void FGMarkerBeacon::stopAudio()
387{
388 if (_audioSampleGroup) {
389 _audioSampleGroup->stop("outer-marker");
390 _audioSampleGroup->stop("middle-marker");
391 _audioSampleGroup->stop("inner-marker");
392 }
393}
394
395void FGMarkerBeacon::valueChanged(SGPropertyNode* val)
396{
397 _audioPropertiesChanged = true;
398}
399
400// Register the subsystem.
401#if 0
402SGSubsystemMgr::InstancedRegistrant<FGMarkerBeacon> registrantFGMarkerBeacon(
403 SGSubsystemMgr::FDM,
404 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}},
405 0.2);
406#endif
#define i(x)
SGSharedPtr< FGPositioned > FGPositionedRef
Definition airways.hxx:30
-- Provides marker beacon audio generation.
void initServicePowerProperties(SGPropertyNode *node)
std::string name() const
void readConfig(SGPropertyNode *config, std::string defaultName)
std::string nodePath() const
void setDefaultPowerSupplyPath(const std::string &p)
specify the default path to use to power the instrument, if it's non- standard.
bool isServiceableAndPowered() const
FGPositioned::Type minType() const override
FGPositioned::Type maxType() const override
SGSoundSample * get_inner()
Definition beacon.hxx:105
BeaconTiming getTimingForOuter() const
Definition beacon.cxx:158
SGSoundSample * get_middle()
Definition beacon.hxx:106
BeaconTiming getTimingForInner() const
Definition beacon.cxx:133
static FGBeacon * instance()
Definition beacon.cxx:122
SGSoundSample * get_outer()
Definition beacon.hxx:107
BeaconTiming getTimingForMiddle() const
Definition beacon.cxx:144
FGMarkerBeacon(SGPropertyNode *node)
void init() override
void unbind() override
void reinit() override
void valueChanged(SGPropertyNode *val) override
void update(double dt) override
void bind() override
Predicate class to support custom filtering of FGPositioned queries Default implementation of this pa...
virtual const SGVec3d & cart() const
The cartesian position associated with this object.
double elevation() const
static FGPositionedRef findClosest(const SGGeod &aPos, double aCutoffNm, Filter *aFilter=NULL)
Find the closest item to a position, which pass the specified filter A cutoff range in NM must be spe...
void fgUntie(const char *name)
Untie a property from an external data source.
Definition fg_props.cxx:634
FGGlobals * globals
Definition globals.cxx:142
static SGSoundSample * createSampleForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
static string sampleNameForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
static void lazyChangeBoolProp(SGPropertyNode *node, bool v)
static bool check_beacon_range(const SGGeod &pos, FGPositioned *b)
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27