]> git.mxchange.org Git - simgear.git/blob - simgear/sound/xmlsound.cxx
Don't continue parsing after processing version line
[simgear.git] / simgear / sound / xmlsound.cxx
1 // sound.cxx -- Sound class implementation
2 //
3 // Started by Erik Hofman, February 2002
4 // (Reuses some code from  fg_fx.cxx created by David Megginson)
5 //
6 // Copyright (C) 2002  Curtis L. Olson - http://www.flightgear.org/~curt
7 //
8 // This program is free software; you can redistribute it and/or
9 // modify it under the terms of the GNU General Public License as
10 // published by the Free Software Foundation; either version 2 of the
11 // License, or (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful, but
14 // WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 // General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
21 //
22 // $Id$
23
24 #ifdef HAVE_CONFIG_H
25 #  include <simgear_config.h>
26 #endif
27
28 #include "xmlsound.hxx"
29
30
31 #include <simgear/compiler.h>
32
33 #include <string.h>
34 #include <stdio.h>
35
36 #include <simgear/debug/logstream.hxx>
37 #include <simgear/props/props.hxx>
38 #include <simgear/props/condition.hxx>
39 #include <simgear/structure/exception.hxx>
40 #include <simgear/misc/sg_path.hxx>
41
42 #include "sample_group.hxx"
43 #include "sample_openal.hxx"
44
45 using std::string;
46
47 // static double _snd_lin(double v)   { return v; }
48 static double _snd_inv(double v)   { return (v == 0) ? 1e99 : 1/v; }
49 static double _snd_abs(double v)   { return (v >= 0) ? v : -v; }
50 static double _snd_sqrt(double v)  { return sqrt(fabs(v)); }
51 static double _snd_log10(double v) { return log10(fabs(v)+1e-9); }
52 static double _snd_log(double v)   { return log(fabs(v)+1e-9); }
53 // static double _snd_sqr(double v)   { return v*v; }
54 // static double _snd_pow3(double v)  { return v*v*v; }
55
56 static const struct {
57         const char *name;
58         double (*fn)(double);
59 } __sound_fn[] = {
60         {"inv", _snd_inv},
61         {"abs", _snd_abs},
62         {"sqrt", _snd_sqrt},
63         {"log", _snd_log10},
64         {"ln", _snd_log},
65         {"", NULL}
66 };
67
68 SGXmlSound::SGXmlSound()
69   : _sample(NULL),
70     _active(false),
71     _name(""),
72     _mode(SGXmlSound::ONCE),
73     _prev_value(0),
74     _dt_play(0.0),
75     _dt_stop(0.0),
76     _delay(0.0),
77     _stopping(0.0),
78     _initialized(false)
79 {
80 }
81
82 SGXmlSound::~SGXmlSound()
83 {
84     if (_sample)
85         _sample->stop();
86
87     _volume.clear();
88     _pitch.clear();
89 }
90
91 void
92 SGXmlSound::init( SGPropertyNode *root,
93                   SGPropertyNode *node,
94                   SGSampleGroup *sgrp,
95                   SGSampleGroup *avionics,
96                   const SGPath& path )
97 {
98
99    //
100    // set global sound properties
101    //
102
103    _name = node->getStringValue("name", "");
104    SG_LOG(SG_SOUND, SG_DEBUG, "Loading sound information for: " << _name );
105
106    string mode_str = node->getStringValue("mode", "");
107    if ( mode_str == "looped" ) {
108        _mode = SGXmlSound::LOOPED;
109
110    } else if ( mode_str == "in-transit" ) {
111        _mode = SGXmlSound::IN_TRANSIT;
112
113    } else {
114       _mode = SGXmlSound::ONCE;
115    }
116
117    bool is_avionics = false;
118    string type_str = node->getStringValue("type", "fx");
119    if ( type_str == "avionics" )
120       is_avionics = true;
121
122    string propval = node->getStringValue("property", "");
123    if (propval != "")
124       _property = root->getNode(propval, true);
125
126    SGPropertyNode *condition = node->getChild("condition");
127    if (condition != NULL)
128       _condition = sgReadCondition(root, condition);
129
130    if (!_property && !_condition)
131       SG_LOG(SG_SOUND, SG_WARN,
132              "  Neither a condition nor a property specified");
133
134    _delay = node->getDoubleValue("delay-sec", 0.0);
135
136    //
137    // set volume properties
138    //
139    unsigned int i;
140    float v = 0.0;
141    std::vector<SGPropertyNode_ptr> kids = node->getChildren("volume");
142    for (i = 0; (i < kids.size()) && (i < SGXmlSound::MAXPROP); i++) {
143       _snd_prop volume = {NULL, NULL, NULL, 1.0, 0.0, 0.0, 0.0, false};
144
145       propval = kids[i]->getStringValue("property", "");
146       if ( propval != "" )
147          volume.prop = root->getNode(propval, true);
148
149       string intern_str = kids[i]->getStringValue("internal", "");
150       if (intern_str == "dt_play")
151          volume.intern = &_dt_play;
152       else if (intern_str == "dt_stop")
153          volume.intern = &_dt_stop;
154
155       if ((volume.factor = kids[i]->getDoubleValue("factor", 1.0)) != 0.0)
156          if (volume.factor < 0.0) {
157             volume.factor = -volume.factor;
158             volume.subtract = true;
159          }
160
161       string type_str = kids[i]->getStringValue("type", "");
162       if ( type_str != "" ) {
163
164          for (int j=0; __sound_fn[j].fn; j++)
165            if ( type_str == __sound_fn[j].name ) {
166                volume.fn = __sound_fn[j].fn;
167                break;
168             }
169
170          if (!volume.fn)
171             SG_LOG(SG_SOUND,SG_INFO,
172                    "  Unknown volume type, default to 'lin'");
173       }
174
175       volume.offset = kids[i]->getDoubleValue("offset", 0.0);
176
177       if ((volume.min = kids[i]->getDoubleValue("min", 0.0)) < 0.0)
178          SG_LOG( SG_SOUND, SG_WARN,
179           "Volume minimum value below 0. Forced to 0.");
180
181       volume.max = kids[i]->getDoubleValue("max", 0.0);
182       if (volume.max && (volume.max < volume.min) )
183          SG_LOG(SG_SOUND,SG_ALERT,
184                 "  Volume maximum below minimum. Neglected.");
185
186       _volume.push_back(volume);
187       v += volume.offset;
188
189    }
190
191    // rule of thumb: make reference distance a 100th of the maximum distance.
192    float reference_dist = node->getDoubleValue("reference-dist", 60.0);
193    float max_dist = node->getDoubleValue("max-dist", 6000.0);
194
195    //
196    // set pitch properties
197    //
198    float p = 0.0;
199    kids = node->getChildren("pitch");
200    for (i = 0; (i < kids.size()) && (i < SGXmlSound::MAXPROP); i++) {
201       _snd_prop pitch = {NULL, NULL, NULL, 1.0, 1.0, 0.0, 0.0, false};
202
203       propval = kids[i]->getStringValue("property", "");
204       if (propval != "")
205          pitch.prop = root->getNode(propval, true);
206
207       string intern_str = kids[i]->getStringValue("internal", "");
208       if (intern_str == "dt_play")
209          pitch.intern = &_dt_play;
210       else if (intern_str == "dt_stop")
211          pitch.intern = &_dt_stop;
212
213       if ((pitch.factor = kids[i]->getDoubleValue("factor", 1.0)) != 0.0)
214          if (pitch.factor < 0.0) {
215             pitch.factor = -pitch.factor;
216             pitch.subtract = true;
217          }
218
219       string type_str = kids[i]->getStringValue("type", "");
220       if ( type_str != "" ) {
221
222          for (int j=0; __sound_fn[j].fn; j++) 
223             if ( type_str == __sound_fn[j].name ) {
224                pitch.fn = __sound_fn[j].fn;
225                break;
226             }
227
228          if (!pitch.fn)
229             SG_LOG(SG_SOUND,SG_INFO,
230                    "  Unknown pitch type, default to 'lin'");
231       }
232      
233       pitch.offset = kids[i]->getDoubleValue("offset", 1.0);
234
235       if ((pitch.min = kids[i]->getDoubleValue("min", 0.0)) < 0.0)
236          SG_LOG(SG_SOUND,SG_WARN,
237                 "  Pitch minimum value below 0. Forced to 0.");
238
239       pitch.max = kids[i]->getDoubleValue("max", 0.0);
240       if (pitch.max && (pitch.max < pitch.min) )
241          SG_LOG(SG_SOUND,SG_ALERT,
242                 "  Pitch maximum below minimum. Neglected");
243
244       _pitch.push_back(pitch);
245       p += pitch.offset;
246    }
247
248    //
249    // Relative position
250    //
251    SGVec3f offset_pos = SGVec3f::zeros();
252    SGPropertyNode_ptr prop = node->getChild("position");
253    SGPropertyNode_ptr pos_prop[3];
254    if ( prop != NULL ) {
255        offset_pos[0] = -prop->getDoubleValue("x", 0.0);
256        offset_pos[1] = -prop->getDoubleValue("y", 0.0);
257        offset_pos[2] = -prop->getDoubleValue("z", 0.0);
258
259        pos_prop[0] = prop->getChild("x");
260        if (pos_prop[0]) pos_prop[0] = pos_prop[0]->getNode("property");
261        if (pos_prop[0]) {
262            pos_prop[0] = root->getNode(pos_prop[0]->getStringValue(), true);
263        }
264        pos_prop[1] = prop->getChild("y");
265        if (pos_prop[1]) pos_prop[1] = pos_prop[1]->getNode("property");
266        if (pos_prop[1]) {
267            pos_prop[1] = root->getNode(pos_prop[1]->getStringValue(), true);
268        }
269        pos_prop[2] = prop->getChild("z");
270        if (pos_prop[2]) pos_prop[2] = pos_prop[1]->getNode("property");
271        if (pos_prop[2]) {
272            pos_prop[2] = root->getNode(pos_prop[2]->getStringValue(), true);
273        }
274    }
275
276    //
277    // Orientation
278    //
279    SGVec3f dir = SGVec3f::zeros();
280    float inner = 360.0;
281    float outer = 360.0;
282    float outer_gain = 0.0;
283    prop = node->getChild("orientation");
284    if ( prop != NULL ) {
285       dir = SGVec3f(-prop->getFloatValue("x", 0.0),
286                     -prop->getFloatValue("y", 0.0),
287                     -prop->getFloatValue("z", 0.0));
288       inner = prop->getFloatValue("inner-angle", 360.0);
289       outer = prop->getFloatValue("outer-angle", 360.0);
290       outer_gain = prop->getFloatValue("outer-gain", 0.0);
291    }
292
293    //
294    // Initialize the sample
295    //
296    if ((is_avionics)&&(avionics)) {
297       _sgrp = avionics;
298    } else {
299       _sgrp = sgrp;
300    }
301    string soundFileStr = node->getStringValue("path", "");
302    _sample = new SGSoundSample(soundFileStr.c_str(), path);
303    if (!_sample->file_path().exists()) {
304       throw sg_io_exception("XML sound: couldn't find file: '" + soundFileStr + "'");
305       return;
306    }
307    
308    _sample->set_relative_position( offset_pos );
309    _sample->set_position_properties( pos_prop );
310    _sample->set_direction( dir );
311    _sample->set_audio_cone( inner, outer, outer_gain );
312    _sample->set_reference_dist( reference_dist );
313    _sample->set_max_dist( max_dist );
314    _sample->set_volume( v );
315    _sample->set_pitch( p );
316    _sgrp->add( _sample, _name );
317 }
318
319 void
320 SGXmlSound::update (double dt)
321 {
322    double curr_value = 0.0;
323
324    //
325    // If the state changes to false, stop playing.
326    //
327    if (_property)
328        curr_value = _property->getDoubleValue();
329
330    if (!_initialized)
331    {
332        // update initial value before detecting changes
333        _prev_value  = curr_value;
334        _initialized = true;
335    }
336
337    // If a condition is defined, test whether it is FALSE,
338    // else
339    //   if a property is defined then test if it's value is FALSE
340    //      or if the mode is IN_TRANSIT then
341    //            test whether the current value matches the previous value.
342    if (                                                 // Lisp, anyone?
343        (_condition && !_condition->test()) ||
344        (!_condition && _property &&
345         (
346          !curr_value ||
347          ( (_mode == SGXmlSound::IN_TRANSIT) && (curr_value == _prev_value) )
348          )
349         )
350        )
351    {
352        if ((_mode != SGXmlSound::IN_TRANSIT) || (_stopping > MAX_TRANSIT_TIME))
353        {
354            if (_sample->is_playing()) {
355                SG_LOG(SG_SOUND, SG_DEBUG, "Stopping audio after " << _dt_play
356                       << " sec: " << _name );
357
358                _sample->stop();
359            }
360
361            _active = false;
362            _dt_stop += dt;
363            _dt_play = 0.0;
364        } else {
365            _stopping += dt;
366        }
367
368        return;
369    }
370
371    //
372    // mode is ONCE and the sound is still playing?
373    //
374    if (_active && (_mode == SGXmlSound::ONCE)) {
375
376       if (!_sample->is_playing()) {
377          _dt_stop += dt;
378          _dt_play = 0.0;
379       } else {
380          _dt_play += dt;
381       }
382
383    } else {
384
385       //
386       // Update the playing time, cache the current value and
387       // clear the delay timer.
388       //
389       _dt_play += dt;
390       _prev_value = curr_value;
391       _stopping = 0.0;
392    }
393
394    if (_dt_play < _delay)
395       return;
396
397    //
398    // Update the volume
399    //
400    int i;
401    int max = _volume.size();
402    double volume = 1.0;
403    double volume_offset = 0.0;
404
405    for(i = 0; i < max; i++) {
406       double v = 1.0;
407
408       if (_volume[i].prop)
409          v = _volume[i].prop->getDoubleValue();
410
411       else if (_volume[i].intern)
412          v = *_volume[i].intern;
413
414       if (_volume[i].fn)
415          v = _volume[i].fn(v);
416
417       v *= _volume[i].factor;
418
419       if (_volume[i].max && (v > _volume[i].max))
420          v = _volume[i].max;
421
422       else if (v < _volume[i].min)
423          v = _volume[i].min;
424
425       if (_volume[i].subtract)                          // Hack!
426          volume = _volume[i].offset - v;
427
428       else {
429          volume_offset += _volume[i].offset;
430          volume *= v;
431       }
432    }
433
434    //
435    // Update the pitch
436    //
437    max = _pitch.size();
438    double pitch = 1.0;
439    double pitch_offset = 0.0;
440
441    for(i = 0; i < max; i++) {
442       double p = 1.0;
443
444       if (_pitch[i].prop)
445          p = _pitch[i].prop->getDoubleValue();
446
447       else if (_pitch[i].intern)
448          p = *_pitch[i].intern;
449
450       if (_pitch[i].fn)
451          p = _pitch[i].fn(p);
452
453       p *= _pitch[i].factor;
454
455       if (_pitch[i].max && (p > _pitch[i].max))
456          p = _pitch[i].max;
457
458       else if (p < _pitch[i].min)
459          p = _pitch[i].min;
460
461       if (_pitch[i].subtract)                           // Hack!
462          pitch = _pitch[i].offset - p;
463
464       else {
465          pitch_offset += _pitch[i].offset;
466          pitch *= p;
467       }
468    }
469
470    //
471    // Change sample state
472    //
473
474    double vol = volume_offset + volume;
475    if (vol > 1.0) {
476       SG_LOG(SG_SOUND, SG_DEBUG, "Sound volume too large for '"
477               << _name << "':  " << vol << "  ->  clipping to 1.0");
478       vol = 1.0;
479    }
480    _sample->set_volume(vol);
481    _sample->set_pitch( pitch_offset + pitch );
482
483
484    //
485    // Do we need to start playing the sample?
486    //
487    if (!_active) {
488
489       if (_mode == SGXmlSound::ONCE)
490          _sample->play(false);
491
492       else
493          _sample->play(true);
494
495       SG_LOG(SG_SOUND, SG_DEBUG, "Playing audio after " << _dt_stop
496                                    << " sec: " << _name);
497       SG_LOG(SG_SOUND, SG_DEBUG,
498                          "Playing " << ((_mode == ONCE) ? "once" : "looped"));
499
500       _active = true;
501       _dt_stop = 0.0;
502    }
503 }