Users Guide to FlightGear sound configuration
-Version 0.7.10, Mar 02 2002
+Version 0.7.11, apr 27 2002
Author: Erik Hofman <erik@ehofman.com>
This document is an attempt to describe the configuration of
<name>engine</name>
<path>Sounds/wasp.wav</path>
<mode>looped</mode>
- <property>/engines/engine/running</property>
+ <condition>
+ <property>/engines/engine/running</property>
+ </condition>
<volume>
<property>/engines/engine/mp-osi</property>
<factor>0.005</factor>
< ... >
This is the event seperator. The text inside the brackets
can be anything. Bit it is adviced to give it a meaningfull name
- like: crank, engine, rumble, gear, squeal, flap, wind or stall
+ like: crank, engine, rumble, gear, squeal, flap, wind or stall
The value can be defined multiple times, thus anything which is
related may have the same name (grouping them together).
<name>
This defines the name of the event. This name is used internally
and, although it can me defined multiple times in the same file,
- should normally have an unique value.
+ should normally have an unique value.
Multiple definitions of the same name will allow multiple sections
to interfere in the starting and stopping of the sample.
Using the type "fall" will stop playback when the event turns true.
IMPORTANT:
- If the trigger is used for anything else but stopping the sound
+ If the trigger is used for anything else but stopping the sound
at a certain event, all sections with the same name *should* have
exactly the same sections for everything but property and type.
<path>
This defined th path to the sound file. The path is relative to the
FlightGear root directory but could be specified absolute.
-
- <property>
- Define which property triggers the event, and reffers to a node
- in the FlightGear property tree.
-
- The value is converted to an integer value (anything less than 0.5 is
- is considered to be 0) and handled if it were a boolean value
- (0 = false, anything else = true).
-
- The triger depends on the value of <type>.
-
- <type>
- This specifies how the event is triggered. When an event is triggered
- the sample will start playing. Since the effects scheduler can have
- multiple events controll a single sound event, it depends on the
- situation if an event actually stops playing the sound.
- Basically the following is true:
- The first event requesting to start playback, triggers playback.
- The last event requesting to stop playback, will stop playback.
+ <condition>
+ Define a condition that triggers the event.
+ For a complete description of the FlightGear conditions,
+ please read docs-mini/README.conditions
- There are multiple options:
-
- level: events are active if the value is true.
- this is the default behaviour.
-
- inverted: events are active if the value is false.
+ An event should define either a condition or a property.
- flipflop: events are triggered on state changes.
- this is only usefull for samples which are played
- once.
-
- raise: start playing at the raise of the event.
- explicitly stop playing when the event turns false.
+ <property>
+ Define which property triggers the event, and reffers to a node
+ in the FlightGear property tree. Action is taken when the property
+ is non zero.
- fall: start playing at the fall of the event.
- explicitly stop playing when the event turns true.
+ A more sophisticated mechanism to trigger the event is described
+ in <condition>
<mode>
This defines how the sample should be played:
looped: the sample plays continuesly,
until the event turns false.
-
+
+ in-transit: the sample plays continuesly,
+ while the property is changing its value.
<volume> / <pitch>
Volume or Pitch definition. Currently there may be up to 5
this is the default.
ln: convert the property value to a natural logarithmic
- value before scaling it.
+ value before scaling it. Anything below 1 will return
+ zero.
log: convert the property value to a true logarithmic
- value before scaling it.
+ value before scaling it. Anything below 1 will return
+ zero.
inv: inverse lineair handling (1/x).
will be truncated to this value.
<max>
-
Maximum allowed value.
This is usefull if sounds gets to loud. Anything higher will be
truncated to this value.
type: lin
factor: 1.0
offset: 0.0 for volume, 1.0 for pitch
-min: 0.0 (don't check)
+min: 0.0
max: 0.0 (don't check)
for (n = 0; n < max; n++) {
if (factor < 0)
{
- value += offset[n] - abs(factor[n]) * function(property[n]);
+ value += offset[n] - abs(factor[n]) * function(property[n]);
}
else
{
- value += factor[n] * function(property[n]);
- offs += offset[n];
+ value += factor[n] * function(property[n]);
+ offs += offset[n];
}
}
static double _fg_inv(double v) { return (v == 0) ? 1e99 : 1/v; };
static double _fg_abs(double v) { return (v >= 0) ? v : -v; };
static double _fg_sqrt(double v) { return (v < 0) ? sqrt(-v) : sqrt(v); };
-static double _fg_log10(double v) { return (v < 1) ? 0 : log10(v+1); };
-static double _fg_log(double v) { return (v < 1) ? 0 : log(v+1); };
+static double _fg_log10(double v) { return (v < 1) ? 0 : log10(v); };
+static double _fg_log(double v) { return (v < 1) ? 0 : log(v); };
// static double _fg_sqr(double v) { return pow(v, 2); };
// static double _fg_pow3(double v) { return pow(v, 3); };
{"", NULL}
};
-FGSound::FGSound(const SGPropertyNode * node)
- : _node(node),
+FGSound::FGSound()
+ : _condition(NULL),
+ _property(NULL),
_sample(NULL),
- _active(false),
_mode(FGSound::ONCE),
- _type(FGSound::LEVEL),
- _name(""),
- _factor(1.0)
+ _prev_value(0),
+ _name("")
{
}
FGSound::~FGSound()
{
+ delete _condition;
delete _sample;
}
void
-FGSound::init()
+FGSound::init(SGPropertyNode *node)
{
- _property = fgGetNode(_node->getStringValue("property"), true);
-
//
// set global sound properties
//
- _name = _node->getStringValue("name");
-
- if ((_factor = _node->getDoubleValue("factor")) == 0.0)
- _factor = 1.0;
-
- _offset = _node->getDoubleValue("offset");
-
+
+ _name = node->getStringValue("name");
SG_LOG(SG_GENERAL, SG_INFO, "Loading sound information for: " << _name );
- const char *mode_str = _node->getStringValue("mode");
- if ( !strcmp(mode_str,"looped") ) {
+ const char *mode_str = node->getStringValue("mode");
+ if ( !strcmp(mode_str, "looped") ) {
_mode = FGSound::LOOPED;
+ } else if ( !strcmp(mode_str, "in-transit") ) {
+ _mode = FGSound::IN_TRANSIT;
+
} else {
_mode = FGSound::ONCE;
SG_LOG(SG_GENERAL,SG_INFO, " Unknown sound mode, default to 'once'");
}
- const char *type_str = _node->getStringValue("type");
- if ( !strcmp(type_str, "flipflop") ) {
- _type = FGSound::FLIPFLOP;
-
- } else if ( !strcmp(type_str, "inverted") ) {
- _type = FGSound::INVERTED;
+ _property = fgGetNode(node->getStringValue("property"), true);
+ SGPropertyNode *condition = node->getChild("condition");
+ if (condition != NULL)
+ _condition = fgReadCondition(condition);
- } else if ( !strcmp(type_str, "raise") ) {
- _type = FGSound::RAISE;
-
- } else if ( !strcmp(type_str, "fall") ) {
- _type = FGSound::FALL;
-
- } else {
- _type = FGSound::LEVEL;
-
- if ( strcmp(type_str, "") )
- SG_LOG(SG_GENERAL,SG_INFO, " Unknown sound type, default to 'level'");
- }
-
-#if 0
- //
- // set position properties
- //
- _pos.dist = _node->getDoubleValue("dist");
- _pos.hor = _node->getDoubleValue("pos_hor");
- _pos.vert = _node->getDoubleValue("pos_vert");
-#endif
+ if (!_property && !_condition)
+ SG_LOG(SG_GENERAL, SG_WARN,
+ " Neither a condition nor a property specified");
//
// set volume properties
//
unsigned int i;
float v = 0.0;
- vector<const SGPropertyNode *> kids = _node->getChildren("volume");
+ vector<SGPropertyNode *> kids = node->getChildren("volume");
for (i = 0; (i < kids.size()) && (i < FGSound::MAXPROP); i++) {
_snd_prop volume;
// set pitch properties
//
float p = 0.0;
- kids = _node->getChildren("pitch");
+ kids = node->getChildren("pitch");
for (i = 0; (i < kids.size()) && (i < FGSound::MAXPROP); i++) {
_snd_prop pitch;
//
// Initialize the sample
//
- FGSoundMgr * mgr = globals->get_soundmgr();
- if ((_sample = mgr->find(_name)) == NULL)
- _sample = mgr->add(_name, _node->getStringValue("path"));
+ _mgr = globals->get_soundmgr();
+ if ((_sample = _mgr->find(_name)) == NULL)
+ _sample = _mgr->add(_name, node->getStringValue("path"));
_sample->set_volume(v);
_sample->set_pitch(p);
void
FGSound::update (int dt)
{
- FGSoundMgr * mgr = globals->get_soundmgr();
+ double curr_value = 0.0;
//
- // Do we have something to do?
+ // If the state changes to false, stop playing.
//
+ if (_property)
+ curr_value = _property->getDoubleValue();
+
+ if ( // Lisp, anyone?
+ (_condition && !_condition->test()) ||
+ (_property &&
+ (
+ !curr_value ||
+ ( (_mode == FGSound::IN_TRANSIT) && (curr_value == _prev_value) )
+ )
+ )
+ )
+ {
- // if (!_property)
- // return;
-
- if ((_type == FGSound::LEVEL) || (_type == FGSound::INVERTED)) {
-
- //
- // use an integer to get false when: -1 < check < 1
- //
- bool check = (int)(_offset + _property->getDoubleValue() * _factor);
- if (_type == FGSound::INVERTED)
- check = !check;
-
- //
- // If the state changes to false, stop playing.
- //
- if (!check) {
- if (_active) {
- SG_LOG(SG_GENERAL, SG_INFO, "Stopping sound: " << _name);
- _sample->stop( mgr->get_scheduler(), false );
- _active = false;
- }
-
- return;
- }
-
- //
- // If the sound is already playing we have nothing to do.
- //
- if (_active && (_mode == FGSound::ONCE))
- return;
-
- } else { // FLIPFLOP, RAISE, FALL
-
- bool check = (int)(_offset + _property->getDoubleValue() * _factor);
- if (check == _active)
- return;
-
- //
- // Check for state changes.
- // If the state changed, and the sound is still playing: stop playing.
- //
+ _active = false;
if (_sample->is_playing()) {
SG_LOG(SG_GENERAL, SG_INFO, "Stopping sound: " << _name);
- _sample->stop( mgr->get_scheduler() );
+ _sample->stop( _mgr->get_scheduler() );
}
- if ( ((_type == FGSound::RAISE) && !check) ||
- ((_type == FGSound::FALL) && check) )
- return;
+ return;
}
- {
- int i, max;
+ //
+ // If the mode is ONCE and the sound is still playing,
+ // we have nothing to do anymore.
+ //
+ if (_active && (_mode == FGSound::ONCE))
+ return;
+
+ //
+ // Cache current value;
+ //
+ _prev_value = curr_value;
- //
- // Update the volume
- //
- max = _volume.size();
- double volume = 1.0;
- double volume_offset = 0.0;
+ //
+ // Update the volume
+ //
+ int i;
+ int max = _volume.size();
+ double volume = 1.0;
+ double volume_offset = 0.0;
- for(i = 0; i < max; i++) {
- double v = _volume[i].prop->getDoubleValue();
+ for(i = 0; i < max; i++) {
+ double v = _volume[i].prop->getDoubleValue();
- if (_volume[i].fn)
- v = _volume[i].fn(v);
+ if (_volume[i].fn)
+ v = _volume[i].fn(v);
- v *= _volume[i].factor;
+ v *= _volume[i].factor;
- if (!_volume[i].max && (v > _volume[i].max))
- v = _volume[i].max;
+ if (_volume[i].max && (v > _volume[i].max))
+ v = _volume[i].max;
- else if (!_volume[i].min && (v < _volume[i].min))
- v = _volume[i].min;
+ else if (v < _volume[i].min)
+ v = _volume[i].min;
- if (_volume[i].subtract) // Hack!
- volume = _volume[i].offset - v;
- else {
- volume_offset += _volume[i].offset;
- volume *= v;
- }
+ if (_volume[i].subtract) // Hack!
+ volume = _volume[i].offset - v;
+
+ else {
+ volume_offset += _volume[i].offset;
+ volume *= v;
}
+ }
- //
- // Update the pitch
- //
- max = _pitch.size();
- double pitch = 1.0;
- double pitch_offset = 0.0;
+ //
+ // Update the pitch
+ //
+ max = _pitch.size();
+ double pitch = 1.0;
+ double pitch_offset = 0.0;
- for(i = 0; i < max; i++) {
- double p = _pitch[i].prop->getDoubleValue();
+ for(i = 0; i < max; i++) {
+ double p = _pitch[i].prop->getDoubleValue();
- if (_pitch[i].fn)
- p = _pitch[i].fn(p);
+ if (_pitch[i].fn)
+ p = _pitch[i].fn(p);
- p *= _pitch[i].factor;
+ p *= _pitch[i].factor;
- if (!_pitch[i].max && (p > _pitch[i].max))
- p = _pitch[i].max;
+ if (_pitch[i].max && (p > _pitch[i].max))
+ p = _pitch[i].max;
- else if (!_pitch[i].min && (p < _pitch[i].min))
- p = _pitch[i].min;
+ else if (p < _pitch[i].min)
+ p = _pitch[i].min;
- if (_pitch[i].subtract) // Hack!
- pitch = _pitch[i].offset - p;
- else {
- pitch_offset += _pitch[i].offset;
- pitch *= p;
- }
- }
+ if (_pitch[i].subtract) // Hack!
+ pitch = _pitch[i].offset - p;
- //
- // Change sample state
- //
- _sample->set_pitch( pitch_offset + pitch );
- _sample->set_volume( volume_offset + volume );
+ else {
+ pitch_offset += _pitch[i].offset;
+ pitch *= p;
+ }
}
//
- // Do we need to start playing the sample?
+ // Change sample state
//
- if (_active && ((_type == FGSound::LEVEL) || (_type == FGSound::INVERTED)))
- return;
+ _sample->set_pitch( pitch_offset + pitch );
+ _sample->set_volume( volume_offset + volume );
+
//
- // This is needed for FGSound::FLIPFLOP and it works for
- // FGSound::LEVEl. Doing it this way saves an extra 'if'.
+ // Do we need to start playing the sample?
//
- _active = !_active;
+ if (!_active) {
- if (_mode == FGSound::ONCE)
- _sample->play(mgr->get_scheduler(), false);
- else
- _sample->play(mgr->get_scheduler(), true);
+ _active = true;
+ if (_mode == FGSound::ONCE)
+ _sample->play(_mgr->get_scheduler(), false);
- SG_LOG(SG_GENERAL, SG_INFO, "Starting audio playback for: " << _name);
- SG_LOG(SG_GENERAL, SG_BULK,
- "Playing " << ((_mode == ONCE) ? "once" : "looped"));
+ else
+ _sample->play(_mgr->get_scheduler(), true);
+
+ SG_LOG(SG_GENERAL, SG_INFO, "Starting audio playback for: " << _name);
+ SG_LOG(SG_GENERAL, SG_BULK,
+ "Playing " << ((_mode == ONCE) ? "once" : "looped"));
+ }
}