1
0
Fork 0

Marker beacons: support correct timing, audio sync.

Allow the option to have the marker beacon instrument use either the
correct timing, or ‘always on’ when the beacon is in range. Try to
ensure the audio is synchronised with the visual property. by using
the same timing values.

Also avoids losing sync when using the ‘audio-btn’ toggle, and increases
the update rate to allow accurate timing. Finally, switch from tied
properties to regular ones, so listeners on the beacon properties are
updated directly.
This commit is contained in:
James Turner 2021-01-28 21:00:45 +00:00
parent 1f5a84df0c
commit ffbf37d198
5 changed files with 287 additions and 174 deletions

View file

@ -166,7 +166,7 @@ bool FGInstrumentMgr::build (SGPropertyNode* config_props)
set_subsystem( id, new MagCompass( node ) );
} else if ( name == "marker-beacon" ) {
set_subsystem( id, new FGMarkerBeacon( node ), 0.2 );
set_subsystem(id, new FGMarkerBeacon(node));
} else if ( name == "comm-radio" ) {
set_subsystem( id, Instrumentation::CommRadio::createInstance( node ) );
@ -179,8 +179,8 @@ bool FGInstrumentMgr::build (SGPropertyNode* config_props)
} else if (( name == "transponder" ) || ( name == "KT-70" )) {
if (name == "KT-70") {
SG_LOG(SG_INSTR, SG_WARN, "KT-70 legacy instrument compatibility. "
"Please update aircraft to use transponder directly");
SG_LOG(SG_INSTR, SG_DEV_ALERT, "KT-70 legacy instrument compatibility. "
"Please update aircraft to use transponder directly");
// force configuration into compatibility mode
node->setBoolValue("kt70-compatibility", true);
}

View file

@ -37,18 +37,49 @@
#include <Sound/beacon.hxx>
#include <string>
using std::string;
static SGSoundSample* createSampleForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
{
switch (ty) {
case FGMarkerBeacon::INNER:
return FGBeacon::instance()->get_inner();
case FGMarkerBeacon::MIDDLE:
return FGBeacon::instance()->get_middle();
case FGMarkerBeacon::OUTER:
return FGBeacon::instance()->get_outer();
default:
return nullptr;
}
}
static string sampleNameForBeacon(FGMarkerBeacon::fgMkrBeacType ty)
{
switch (ty) {
case FGMarkerBeacon::INNER: return "inner-marker";
case FGMarkerBeacon::MIDDLE: return "middle-marker";
case FGMarkerBeacon::OUTER: return "outer-marker";
default:
return {};
}
}
// Constructor
FGMarkerBeacon::FGMarkerBeacon(SGPropertyNode *node) :
outer_blink(false),
middle_blink(false),
inner_blink(false),
_time_before_search_sec(0.0)
{
// backwards-compatability supply path
setDefaultPowerSupplyPath("/systems/electrical/outputs/nav[0]");
readConfig(node, "marker-beacon");
string blinkMode = node->getStringValue("blink-mode");
if (blinkMode == "standard") {
_blinkMode = BlinkMode::Standard;
} else if (blinkMode == "continuous") {
_blinkMode = BlinkMode::Continuous;
}
}
@ -66,9 +97,6 @@ FGMarkerBeacon::init ()
// Inputs
sound_working = fgGetNode("/sim/sound/working", true);
lon_node = fgGetNode("/position/longitude-deg", true);
lat_node = fgGetNode("/position/latitude-deg", true);
alt_node = fgGetNode("/position/altitude-ft", true);
audio_btn = node->getChild("audio-btn", 0, true);
audio_vol = node->getChild("volume", 0, true);
@ -76,8 +104,14 @@ FGMarkerBeacon::init ()
audio_btn->setBoolValue( true );
SGSoundMgr *smgr = globals->get_subsystem<SGSoundMgr>();
_sgr = smgr->find("avionics", true);
_sgr->tie_to_listener();
if (smgr) {
_audioSampleGroup = smgr->find("avionics", true);
_audioSampleGroup->tie_to_listener();
sound_working->addChangeListener(this);
audio_btn->addChangeListener(this);
audio_vol->addChangeListener(this);
}
reinit();
}
@ -85,9 +119,9 @@ FGMarkerBeacon::init ()
void
FGMarkerBeacon::reinit ()
{
blink.stamp();
outer_marker = middle_marker = inner_marker = false;
_time_before_search_sec = 0.0;
_lastBeacon = NOBEACON;
updateOutputProperties(false);
}
void
@ -95,14 +129,9 @@ FGMarkerBeacon::bind ()
{
string branch = nodePath();
fgTie((branch + "/inner").c_str(), this,
&FGMarkerBeacon::get_inner_blink);
fgTie((branch + "/middle").c_str(), this,
&FGMarkerBeacon::get_middle_blink);
fgTie((branch + "/outer").c_str(), this,
&FGMarkerBeacon::get_outer_blink);
_innerBlinkNode = fgGetNode(branch + "/inner", true);
_middleBlinkNode = fgGetNode(branch + "/middle", true);
_outerBlinkNode = fgGetNode(branch + "/outer", true);
}
@ -114,62 +143,97 @@ FGMarkerBeacon::unbind ()
fgUntie((branch + "/inner").c_str());
fgUntie((branch + "/middle").c_str());
fgUntie((branch + "/outer").c_str());
if (_audioSampleGroup) {
sound_working->removeChangeListener(this);
audio_btn->removeChangeListener(this);
audio_vol->removeChangeListener(this);
}
AbstractInstrument::unbind();
}
// Update the various nav values based on position and valid tuned in navs
void
FGMarkerBeacon::update(double dt)
{
// On timeout, scan again, this needs to run every iteration no
// matter what the power or serviceable state. If power is turned
// off or the unit becomes unserviceable while a beacon sound is
// playing, the search() routine still needs to be called so the
// sound effect can be properly disabled.
if (!isServiceableAndPowered()) {
_lastBeacon = NOBEACON;
stopAudio();
updateOutputProperties(false);
return;
}
_time_before_search_sec -= dt;
if ( _time_before_search_sec < 0 ) {
search();
}
if ( isServiceableAndPowered() && sound_working->getBoolValue()) {
if (_audioPropertiesChanged) {
updateAudio();
}
// marker beacon blinking
bool light_on = ( outer_blink || middle_blink || inner_blink );
SGTimeStamp current = SGTimeStamp::now();
if (_lastBeacon != NOBEACON) {
// compute blink to match audio
// we use our own timing here (instead of dt) since audio rate is not affected
// by pause or time acceleration, so this should stay in sync.
const int elapasedUSec = (SGTimeStamp::now() - _audioStartTime).toUSecs();
if ( light_on && blink + SGTimeStamp::fromUSec(400000) < current ) {
light_on = false;
blink = current;
} else if ( !light_on && blink + SGTimeStamp::fromUSec(100000) < current ) {
light_on = true;
blink = current;
bool on = true;
if (_blinkMode != BlinkMode::Continuous) {
int t = elapasedUSec % _beaconTiming.durationUSec;
for (int i = 0; i < 4; i++) {
t -= _beaconTiming.periodsUSec.at(i);
if (t < 0) {
// if value is negative, current time is within this
// period, so we are finished.
break;
}
// each period, the sense flips
on = !on;
} // of periods iteration
}
if ( outer_marker ) {
outer_blink = light_on;
} else {
outer_blink = false;
}
updateOutputProperties(on);
}
}
if ( middle_marker ) {
middle_blink = light_on;
} else {
middle_blink = false;
}
static void lazyChangeBoolProp(SGPropertyNode* node, bool v)
{
if (node->getBoolValue() != v) {
node->setBoolValue(v);
}
}
if ( inner_marker ) {
inner_blink = light_on;
} else {
inner_blink = false;
}
void FGMarkerBeacon::updateOutputProperties(bool on)
{
// map our beacon nodes to indices which correspond to the fgMkrBeacType enum
// this allows to use '_lastBeacon' to select whhich index should be on
// we set all other ones to off to ensure consistency in weird cases, eg
// going from one beaon type to another in a single update.
SGPropertyNode* beacons[4] = {nullptr, _innerBlinkNode.get(), _middleBlinkNode.get(), _outerBlinkNode.get()};
for (int b = INNER; b <= OUTER; b++) {
const bool bOn = on && (_lastBeacon == b);
lazyChangeBoolProp(beacons[b], bOn);
}
}
// cout << outer_blink << " " << middle_blink << " "
// << inner_blink << endl;
} else {
inner_blink = middle_blink = outer_blink = false;
void FGMarkerBeacon::updateAudio()
{
_audioPropertiesChanged = false;
if (!_audioSampleGroup)
return;
float volume = audio_vol->getFloatValue();
if (!audio_btn->getBoolValue()) {
// mute rather than stop, so we don't lose sync with the visual blink
volume = 0.0;
}
SGSoundSample* mkr = _audioSampleGroup->find(sampleNameForBeacon(_lastBeacon));
if (mkr) {
mkr->set_volume(volume);
}
}
@ -215,31 +279,24 @@ static bool check_beacon_range( const SGGeod& pos,
class BeaconFilter : public FGPositioned::Filter
{
public:
virtual FGPositioned::Type minType() const {
return FGPositioned::OM;
}
virtual FGPositioned::Type maxType() const {
return FGPositioned::IM;
}
FGPositioned::Type minType() const override
{
return FGPositioned::OM;
}
FGPositioned::Type maxType() const override
{
return FGPositioned::IM;
}
};
// Update current nav/adf radio stations based on current postition
void FGMarkerBeacon::search()
{
// reset search time
_time_before_search_sec = 1.0;
_time_before_search_sec = 0.5;
static fgMkrBeacType last_beacon = NOBEACON;
SGGeod pos = SGGeod::fromDegFt(lon_node->getDoubleValue(),
lat_node->getDoubleValue(),
alt_node->getDoubleValue());
////////////////////////////////////////////////////////////////////////
// Beacons.
////////////////////////////////////////////////////////////////////////
const SGGeod pos = globals->get_aircraft_position();
// get closest marker beacon - within a 1nm cutoff
BeaconFilter filter;
@ -258,89 +315,84 @@ void FGMarkerBeacon::search()
inrange = check_beacon_range( pos, b.ptr() );
}
outer_marker = middle_marker = inner_marker = false;
if ( b == NULL || !inrange || !isServiceableAndPowered())
{
// cout << "no marker" << endl;
_sgr->stop( "outer-marker" );
_sgr->stop( "middle-marker" );
_sgr->stop( "inner-marker" );
} else {
beacon_type = NOBEACON;
}
string current_sound_name;
changeBeaconType(beacon_type);
}
if ( beacon_type == OUTER ) {
outer_marker = true;
current_sound_name = "outer-marker";
// cout << "OUTER MARKER" << endl;
if ( last_beacon != OUTER ) {
if ( ! _sgr->exists( current_sound_name ) ) {
SGSoundSample *sound = FGBeacon::instance()->get_outer();
if ( sound ) {
_sgr->add( sound, current_sound_name );
}
}
}
if ( audio_btn->getBoolValue() ) {
if ( !_sgr->is_playing(current_sound_name) ) {
_sgr->play_looped( current_sound_name );
}
} else {
_sgr->stop( current_sound_name );
}
} else if ( beacon_type == MIDDLE ) {
middle_marker = true;
current_sound_name = "middle-marker";
// cout << "MIDDLE MARKER" << endl;
if ( last_beacon != MIDDLE ) {
if ( ! _sgr->exists( current_sound_name ) ) {
SGSoundSample *sound = FGBeacon::instance()->get_middle();
if ( sound ) {
_sgr->add( sound, current_sound_name );
}
}
}
if ( audio_btn->getBoolValue() ) {
if ( !_sgr->is_playing(current_sound_name) ) {
_sgr->play_looped( current_sound_name );
}
} else {
_sgr->stop( current_sound_name );
}
} else if ( beacon_type == INNER ) {
inner_marker = true;
current_sound_name = "inner-marker";
// cout << "INNER MARKER" << endl;
if ( last_beacon != INNER ) {
if ( ! _sgr->exists( current_sound_name ) ) {
SGSoundSample *sound = FGBeacon::instance()->get_inner();
if ( sound ) {
_sgr->add( sound, current_sound_name );
}
}
}
if ( audio_btn->getBoolValue() ) {
if ( !_sgr->is_playing(current_sound_name) ) {
_sgr->play_looped( current_sound_name );
}
} else {
_sgr->stop( current_sound_name );
void FGMarkerBeacon::changeBeaconType(fgMkrBeacType newType)
{
if (newType == _lastBeacon)
return;
_lastBeacon = newType;
stopAudio(); // stop any existing playback
if (newType == NOBEACON) {
updateOutputProperties(false);
return;
}
if (_blinkMode == BlinkMode::Standard) {
// get correct timings from the sounds generator
switch (newType) {
case INNER:
_beaconTiming = FGBeacon::instance()->getTimingForInner();
break;
case MIDDLE:
_beaconTiming = FGBeacon::instance()->getTimingForMiddle();
break;
case OUTER:
_beaconTiming = FGBeacon::instance()->getTimingForOuter();
break;
default:
break;
}
} else if (_blinkMode == BlinkMode::BackwardsCompatible) {
// older FG versions used same timing for alll beacon types :(
_beaconTiming = FGBeacon::BeaconTiming{};
_beaconTiming.durationUSec = 500000;
_beaconTiming.periodsUSec[0] = 400000;
_beaconTiming.periodsUSec[1] = 100000;
}
if (_audioSampleGroup) {
// create sample as required
const auto name = sampleNameForBeacon(newType);
if (!_audioSampleGroup->exists(name)) {
SGSoundSample* sound = createSampleForBeacon(newType);
if (sound) {
_audioSampleGroup->add(sound, name);
}
}
// cout << "VOLUME " << audio_vol->getDoubleValue() << endl;
SGSoundSample * mkr = _sgr->find( current_sound_name );
if (mkr)
mkr->set_volume( audio_vol->getFloatValue() );
_audioSampleGroup->play_looped(name);
updateAudio(); // sync volume+mute now
}
if ( inrange ) {
last_beacon = beacon_type;
} else {
last_beacon = NOBEACON;
// we use this timing for visuals as well, so do this even if we have
// no audio sample group
_audioStartTime.stamp();
}
void FGMarkerBeacon::stopAudio()
{
if (_audioSampleGroup) {
_audioSampleGroup->stop("outer-marker");
_audioSampleGroup->stop("middle-marker");
_audioSampleGroup->stop("inner-marker");
}
}
void FGMarkerBeacon::valueChanged(SGPropertyNode* val)
{
_audioPropertiesChanged = true;
}
// Register the subsystem.
#if 0
SGSubsystemMgr::InstancedRegistrant<FGMarkerBeacon> registrantFGMarkerBeacon(

View file

@ -27,34 +27,14 @@
#include <simgear/compiler.h>
#include <Instrumentation/AbstractInstrument.hxx>
#include <Sound/beacon.hxx>
#include <simgear/timing/timestamp.hxx>
class SGSampleGroup;
class FGMarkerBeacon : public AbstractInstrument
class FGMarkerBeacon : public AbstractInstrument,
public SGPropertyChangeListener
{
// Inputs
SGPropertyNode_ptr lon_node;
SGPropertyNode_ptr lat_node;
SGPropertyNode_ptr alt_node;
SGPropertyNode_ptr audio_btn;
SGPropertyNode_ptr audio_vol;
SGPropertyNode_ptr sound_working;
bool outer_marker;
bool middle_marker;
bool inner_marker;
SGTimeStamp blink;
bool outer_blink;
bool middle_blink;
bool inner_blink;
// internal periodic station search timer
double _time_before_search_sec;
SGSharedPtr<SGSampleGroup> _sgr;
public:
enum fgMkrBeacType {
NOBEACON = 0,
@ -78,10 +58,43 @@ public:
void search ();
// Marker Beacon Accessors
inline bool get_inner_blink () const { return inner_blink; }
inline bool get_middle_blink () const { return middle_blink; }
inline bool get_outer_blink () const { return outer_blink; }
void valueChanged(SGPropertyNode* val) override;
private:
// Inputs
SGPropertyNode_ptr audio_btn;
SGPropertyNode_ptr audio_vol;
SGPropertyNode_ptr sound_working;
SGPropertyNode_ptr _innerBlinkNode;
SGPropertyNode_ptr _middleBlinkNode;
SGPropertyNode_ptr _outerBlinkNode;
bool _audioPropertiesChanged = true;
// internal periodic station search timer
double _time_before_search_sec = 0.0;
SGTimeStamp _audioStartTime;
SGSharedPtr<SGSampleGroup> _audioSampleGroup;
enum class BlinkMode {
BackwardsCompatible, ///< all beacons use the OM blink rate
Standard, ///< beacones use the correct blink for their type
Continuous ///< blink disabled, so aircraft can do its own blink
};
BlinkMode _blinkMode = BlinkMode::BackwardsCompatible;
void changeBeaconType(fgMkrBeacType newType);
void updateAudio();
void stopAudio();
void updateOutputProperties(bool on);
fgMkrBeacType _lastBeacon;
FGBeacon::BeaconTiming _beaconTiming;
};

View file

@ -123,3 +123,41 @@ FGBeacon * FGBeacon::instance()
}
return _instance;
}
static const uint64_t sizeToUSec = 1000000UL / FGBeacon::BYTES_PER_SECOND;
FGBeacon::BeaconTiming FGBeacon::getTimingForInner() const
{
BeaconTiming r;
const uint64_t ditLen = INNER_DIT_LEN * sizeToUSec;
r.durationUSec = ditLen;
r.periodsUSec[0] = ditLen / 2;
r.periodsUSec[1] = ditLen / 2;
return r;
}
FGBeacon::BeaconTiming FGBeacon::getTimingForMiddle() const
{
BeaconTiming r;
const uint64_t ditLen = MIDDLE_DIT_LEN * sizeToUSec;
const uint64_t dahLen = MIDDLE_DAH_LEN * sizeToUSec;
r.durationUSec = MIDDLE_SIZE * sizeToUSec;
r.periodsUSec[0] = ditLen;
r.periodsUSec[1] = ditLen;
r.periodsUSec[2] = (dahLen * 3) / 4;
r.periodsUSec[3] = dahLen - r.periodsUSec[2];
return r;
}
FGBeacon::BeaconTiming FGBeacon::getTimingForOuter() const
{
BeaconTiming r;
const uint64_t dahLen = OUTER_DAH_LEN * sizeToUSec;
r.durationUSec = dahLen;
r.periodsUSec[0] = (dahLen * 3) / 4;
r.periodsUSec[1] = dahLen - r.periodsUSec[0];
return r;
}

View file

@ -24,6 +24,8 @@
#ifndef _BEACON_HXX
#define _BEACON_HXX
#include <array>
#include "soundgenerator.hxx"
#include <simgear/compiler.h>
@ -103,7 +105,15 @@ public:
SGSoundSample *get_inner() { return inner; }
SGSoundSample *get_middle() { return middle; }
SGSoundSample *get_outer() { return outer; }
struct BeaconTiming {
uint64_t durationUSec;
std::array<uint64_t, 4> periodsUSec;
};
BeaconTiming getTimingForInner() const;
BeaconTiming getTimingForMiddle() const;
BeaconTiming getTimingForOuter() const;
};