xmlsound.cxx

00001 // sound.cxx -- Sound class implementation
00002 //
00003 // Started by Erik Hofman, February 2002
00004 // (Reuses some code from  fg_fx.cxx created by David Megginson)
00005 //
00006 // Copyright (C) 2002  Curtis L. Olson - http://www.flightgear.org/~curt
00007 //
00008 // This program is free software; you can redistribute it and/or
00009 // modify it under the terms of the GNU General Public License as
00010 // published by the Free Software Foundation; either version 2 of the
00011 // License, or (at your option) any later version.
00012 //
00013 // This program is distributed in the hope that it will be useful, but
00014 // WITHOUT ANY WARRANTY; without even the implied warranty of
00015 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016 // General Public License for more details.
00017 //
00018 // You should have received a copy of the GNU General Public License
00019 // along with this program; if not, write to the Free Software
00020 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
00021 //
00022 // $Id: xmlsound_8cxx-source.html,v 1.7 2007-12-17 15:37:17 curt Exp $
00023 
00024 #ifdef HAVE_CONFIG_H
00025 #  include <simgear_config.h>
00026 #endif
00027 
00028 #include <simgear/compiler.h>
00029 
00030 #include <string.h>
00031 
00032 #include <simgear/debug/logstream.hxx>
00033 #include <simgear/props/condition.hxx>
00034 #include <simgear/math/SGMath.hxx>
00035 
00036 
00037 #include "xmlsound.hxx"
00038 
00039 
00040 // static double _snd_lin(double v)   { return v; }
00041 static double _snd_inv(double v)   { return (v == 0) ? 1e99 : 1/v; }
00042 static double _snd_abs(double v)   { return (v >= 0) ? v : -v; }
00043 static double _snd_sqrt(double v)  { return sqrt(fabs(v)); }
00044 static double _snd_log10(double v) { return log10(fabs(v)); }
00045 static double _snd_log(double v)   { return log(fabs(v)); }
00046 // static double _snd_sqr(double v)   { return v*v; }
00047 // static double _snd_pow3(double v)  { return v*v*v; }
00048 
00049 static const struct {
00050         char *name;
00051         double (*fn)(double);
00052 } __sound_fn[] = {
00053 //      {"lin", _snd_lin},
00054         {"inv", _snd_inv},
00055         {"abs", _snd_abs},
00056         {"sqrt", _snd_sqrt},
00057         {"log", _snd_log10},
00058         {"ln", _snd_log},
00059 //      {"sqr", _snd_sqr},
00060 //      {"pow3", _snd_pow3},
00061         {"", NULL}
00062 };
00063 
00064 SGXmlSound::SGXmlSound()
00065   : _sample(NULL),
00066     _condition(NULL),
00067     _active(false),
00068     _name(""),
00069     _mode(SGXmlSound::ONCE),
00070     _prev_value(0),
00071     _dt_play(0.0),
00072     _dt_stop(0.0),
00073     _stopping(0.0)
00074 {
00075 }
00076 
00077 SGXmlSound::~SGXmlSound()
00078 {
00079     if (_sample)
00080         _sample->stop();
00081 
00082     delete _condition;
00083 
00084     _volume.clear();
00085     _pitch.clear();
00086 }
00087 
00088 void
00089 SGXmlSound::init(SGPropertyNode *root, SGPropertyNode *node, SGSoundMgr *sndmgr,
00090                  const string &path)
00091 {
00092 
00093    //
00094    // set global sound properties
00095    //
00096    
00097    _name = node->getStringValue("name", "");
00098    SG_LOG(SG_GENERAL, SG_INFO, "Loading sound information for: " << _name );
00099 
00100    const char *mode_str = node->getStringValue("mode", "");
00101    if ( !strcmp(mode_str, "looped") ) {
00102        _mode = SGXmlSound::LOOPED;
00103 
00104    } else if ( !strcmp(mode_str, "in-transit") ) {
00105        _mode = SGXmlSound::IN_TRANSIT;
00106 
00107    } else {
00108       _mode = SGXmlSound::ONCE;
00109 
00110       if ( strcmp(mode_str, "") )
00111          SG_LOG(SG_GENERAL,SG_INFO, "  Unknown sound mode, default to 'once'");
00112    }
00113 
00114    _property = root->getNode(node->getStringValue("property", ""), true);
00115    SGPropertyNode *condition = node->getChild("condition");
00116    if (condition != NULL)
00117       _condition = sgReadCondition(root, condition);
00118 
00119    if (!_property && !_condition)
00120       SG_LOG(SG_GENERAL, SG_WARN,
00121              "  Neither a condition nor a property specified");
00122 
00123    //
00124    // set volume properties
00125    //
00126    unsigned int i;
00127    float v = 0.0;
00128    vector<SGPropertyNode_ptr> kids = node->getChildren("volume");
00129    for (i = 0; (i < kids.size()) && (i < SGXmlSound::MAXPROP); i++) {
00130       _snd_prop volume = {NULL, NULL, NULL, 1.0, 0.0, 0.0, 0.0, false};
00131 
00132       if (strcmp(kids[i]->getStringValue("property"), ""))
00133          volume.prop = root->getNode(kids[i]->getStringValue("property", ""), true);
00134 
00135       const char *intern_str = kids[i]->getStringValue("internal", "");
00136       if (!strcmp(intern_str, "dt_play"))
00137          volume.intern = &_dt_play;
00138       else if (!strcmp(intern_str, "dt_stop"))
00139          volume.intern = &_dt_stop;
00140 
00141       if ((volume.factor = kids[i]->getDoubleValue("factor", 1.0)) != 0.0)
00142          if (volume.factor < 0.0) {
00143             volume.factor = -volume.factor;
00144             volume.subtract = true;
00145          }
00146 
00147       const char *type_str = kids[i]->getStringValue("type", "");
00148       if ( strcmp(type_str, "") ) {
00149 
00150          for (int j=0; __sound_fn[j].fn; j++)
00151            if ( !strcmp(type_str, __sound_fn[j].name) ) {
00152                volume.fn = __sound_fn[j].fn;
00153                break;
00154             }
00155 
00156          if (!volume.fn)
00157             SG_LOG(SG_GENERAL,SG_INFO,
00158                    "  Unknown volume type, default to 'lin'");
00159       }
00160 
00161       volume.offset = kids[i]->getDoubleValue("offset", 0.0);
00162 
00163       if ((volume.min = kids[i]->getDoubleValue("min", 0.0)) < 0.0)
00164          SG_LOG( SG_GENERAL, SG_WARN,
00165           "Volume minimum value below 0. Forced to 0.");
00166 
00167       volume.max = kids[i]->getDoubleValue("max", 0.0);
00168       if (volume.max && (volume.max < volume.min) )
00169          SG_LOG(SG_GENERAL,SG_ALERT,
00170                 "  Volume maximum below minimum. Neglected.");
00171 
00172       _volume.push_back(volume);
00173       v += volume.offset;
00174 
00175    }
00176 
00177    float reference_dist = node->getDoubleValue("reference-dist", 500.0);
00178    float max_dist = node->getDoubleValue("max-dist", 3000.0);
00179  
00180    //
00181    // set pitch properties
00182    //
00183    float p = 0.0;
00184    kids = node->getChildren("pitch");
00185    for (i = 0; (i < kids.size()) && (i < SGXmlSound::MAXPROP); i++) {
00186       _snd_prop pitch = {NULL, NULL, NULL, 1.0, 1.0, 0.0, 0.0, false};
00187 
00188       if (strcmp(kids[i]->getStringValue("property", ""), ""))
00189          pitch.prop = root->getNode(kids[i]->getStringValue("property", ""), true);
00190 
00191       const char *intern_str = kids[i]->getStringValue("internal", "");
00192       if (!strcmp(intern_str, "dt_play"))
00193          pitch.intern = &_dt_play;
00194       else if (!strcmp(intern_str, "dt_stop"))
00195          pitch.intern = &_dt_stop;
00196 
00197       if ((pitch.factor = kids[i]->getDoubleValue("factor", 1.0)) != 0.0)
00198          if (pitch.factor < 0.0) {
00199             pitch.factor = -pitch.factor;
00200             pitch.subtract = true;
00201          }
00202 
00203       const char *type_str = kids[i]->getStringValue("type", "");
00204       if ( strcmp(type_str, "") ) {
00205 
00206          for (int j=0; __sound_fn[j].fn; j++) 
00207             if ( !strcmp(type_str, __sound_fn[j].name) ) {
00208                pitch.fn = __sound_fn[j].fn;
00209                break;
00210             }
00211 
00212          if (!pitch.fn)
00213             SG_LOG(SG_GENERAL,SG_INFO,
00214                    "  Unknown pitch type, default to 'lin'");
00215       }
00216      
00217       pitch.offset = kids[i]->getDoubleValue("offset", 1.0);
00218 
00219       if ((pitch.min = kids[i]->getDoubleValue("min", 0.0)) < 0.0)
00220          SG_LOG(SG_GENERAL,SG_WARN,
00221                 "  Pitch minimum value below 0. Forced to 0.");
00222 
00223       pitch.max = kids[i]->getDoubleValue("max", 0.0);
00224       if (pitch.max && (pitch.max < pitch.min) )
00225          SG_LOG(SG_GENERAL,SG_ALERT,
00226                 "  Pitch maximum below minimum. Neglected");
00227 
00228       _pitch.push_back(pitch);
00229       p += pitch.offset;
00230    }
00231 
00232    //
00233    // Relative position
00234    //
00235    sgVec3 offset_pos;
00236    sgSetVec3( offset_pos, 0.0, 0.0, 0.0 );
00237    SGPropertyNode_ptr pos = node->getChild("position");
00238    if ( pos != NULL ) {
00239        offset_pos[0] = pos->getDoubleValue("x", 0.0);
00240        offset_pos[1] = -pos->getDoubleValue("y", 0.0);
00241        offset_pos[2] = pos->getDoubleValue("z", 0.0);
00242    }
00243 
00244    //
00245    // Orientation
00246    //
00247    sgVec3 dir;
00248    float inner, outer, outer_gain;
00249    sgSetVec3( dir, 0.0, 0.0, 0.0 );
00250    inner = outer = 360.0;
00251    outer_gain = 0.0;
00252    pos = node->getChild("orientation");
00253    if ( pos != NULL ) {
00254       dir[0] = pos->getDoubleValue("x", 0.0);
00255       dir[1] = -pos->getDoubleValue("y", 0.0);
00256       dir[2] = pos->getDoubleValue("z", 0.0);
00257       inner = pos->getDoubleValue("inner-angle", 360.0);
00258       outer = pos->getDoubleValue("outer-angle", 360.0);
00259       outer_gain = pos->getDoubleValue("outer-gain", 0.0);
00260    }
00261    
00262    //
00263    // Initialize the sample
00264    //
00265    _mgr = sndmgr;
00266    if ( (_sample = _mgr->find(_name)) == NULL ) {
00267        // FIXME: Does it make sense to overwrite a previous entry's
00268        // configuration just because a new entry has the same name?
00269        // Note that we can't match on identical "path" because we the
00270        // new entry could be at a different location with different
00271        // configuration so we need a new sample which creates a new
00272        // "alSource".  The semantics of what is going on here seems
00273        // confused and needs to be thought through more carefully.
00274         _sample = new SGSoundSample( path.c_str(),
00275                                     node->getStringValue("path", ""),
00276                                     false );
00277 
00278        _mgr->add( _sample, _name );
00279    }
00280 
00281    _sample->set_offset_pos( offset_pos );
00282    _sample->set_orientation(dir, inner, outer, outer_gain);
00283    _sample->set_volume(v);
00284    _sample->set_reference_dist( reference_dist );
00285    _sample->set_max_dist( max_dist );
00286    _sample->set_pitch(p);
00287 }
00288 
00289 void
00290 SGXmlSound::update (double dt)
00291 {
00292    double curr_value = 0.0;
00293 
00294    //
00295    // If the state changes to false, stop playing.
00296    //
00297    if (_property)
00298        curr_value = _property->getDoubleValue();
00299 
00300    if (                                                 // Lisp, anyone?
00301        (_condition && !_condition->test()) ||
00302        (!_condition && _property &&
00303         (
00304          !curr_value ||
00305          ( (_mode == SGXmlSound::IN_TRANSIT) && (curr_value == _prev_value) )
00306          )
00307         )
00308        )
00309    {
00310        if ((_mode != SGXmlSound::IN_TRANSIT) || (_stopping > MAX_TRANSIT_TIME)) {
00311            if (_sample->is_playing()) {
00312                SG_LOG(SG_GENERAL, SG_INFO, "Stopping audio after " << _dt_play
00313                       << " sec: " << _name );
00314 
00315                _sample->stop();
00316            }
00317 
00318            _active = false;
00319            _dt_stop += dt;
00320            _dt_play = 0.0;
00321        } else {
00322            _stopping += dt;
00323        }
00324 
00325        return;
00326    }
00327 
00328    //
00329    // If the mode is ONCE and the sound is still playing,
00330    //  we have nothing to do anymore.
00331    //
00332    if (_active && (_mode == SGXmlSound::ONCE)) {
00333 
00334       if (!_sample->is_playing()) {
00335          _dt_stop += dt;
00336          _dt_play = 0.0;
00337       } else {
00338          _dt_play += dt;
00339       }
00340 
00341       return;
00342    }
00343 
00344    //
00345    // Update the playing time, cache the current value and
00346    // clear the delay timer.
00347    //
00348    _dt_play += dt;
00349    _prev_value = curr_value;
00350    _stopping = 0.0;
00351 
00352    //
00353    // Update the volume
00354    //
00355    int i;
00356    int max = _volume.size();
00357    double volume = 1.0;
00358    double volume_offset = 0.0;
00359 
00360    for(i = 0; i < max; i++) {
00361       double v = 1.0;
00362 
00363       if (_volume[i].prop)
00364          v = _volume[i].prop->getDoubleValue();
00365 
00366       else if (_volume[i].intern)
00367          v = *_volume[i].intern;
00368 
00369       if (_volume[i].fn)
00370          v = _volume[i].fn(v);
00371 
00372       v *= _volume[i].factor;
00373 
00374       if (_volume[i].max && (v > _volume[i].max))
00375          v = _volume[i].max;
00376 
00377       else if (v < _volume[i].min)
00378          v = _volume[i].min;
00379 
00380       if (_volume[i].subtract)                          // Hack!
00381          volume = _volume[i].offset - v;
00382 
00383       else {
00384          volume_offset += _volume[i].offset;
00385          volume *= v;
00386       }
00387    }
00388 
00389    //
00390    // Update the pitch
00391    //
00392    max = _pitch.size();
00393    double pitch = 1.0;
00394    double pitch_offset = 0.0;
00395 
00396    for(i = 0; i < max; i++) {
00397       double p = 1.0;
00398 
00399       if (_pitch[i].prop)
00400          p = _pitch[i].prop->getDoubleValue();
00401 
00402       else if (_pitch[i].intern)
00403          p = *_pitch[i].intern;
00404 
00405       if (_pitch[i].fn)
00406          p = _pitch[i].fn(p);
00407 
00408       p *= _pitch[i].factor;
00409 
00410       if (_pitch[i].max && (p > _pitch[i].max))
00411          p = _pitch[i].max;
00412 
00413       else if (p < _pitch[i].min)
00414          p = _pitch[i].min;
00415 
00416       if (_pitch[i].subtract)                           // Hack!
00417          pitch = _pitch[i].offset - p;
00418 
00419       else {
00420          pitch_offset += _pitch[i].offset;
00421          pitch *= p;
00422       }
00423    }
00424 
00425    //
00426    // Change sample state
00427    //
00428 
00429    double vol = volume_offset + volume;
00430    if (vol > 1.0) {
00431       SG_LOG(SG_GENERAL, SG_WARN, "Sound volume too large for '"
00432               << _name << "':  " << vol << "  ->  clipping to 1.0");
00433       vol = 1.0;
00434    }
00435    _sample->set_volume(vol);
00436    _sample->set_pitch( pitch_offset + pitch );
00437 
00438 
00439    //
00440    // Do we need to start playing the sample?
00441    //
00442    if (!_active) {
00443 
00444       if (_mode == SGXmlSound::ONCE)
00445          _sample->play(false);
00446 
00447       else
00448          _sample->play(true);
00449 
00450       SG_LOG(SG_GENERAL, SG_INFO, "Playing audio after " << _dt_stop 
00451                                    << " sec: " << _name);
00452       SG_LOG(SG_GENERAL, SG_BULK,
00453                          "Playing " << ((_mode == ONCE) ? "once" : "looped"));
00454 
00455       _active = true;
00456       _dt_stop = 0.0;
00457    }
00458 }

Generated on Mon Dec 17 09:30:56 2007 for SimGear by  doxygen 1.5.1