]> git.mxchange.org Git - flightgear.git/blob - src/Sound/fg_sound.cxx
Erik Hofman:
[flightgear.git] / src / Sound / fg_sound.cxx
1 // fg_sound.hxx -- 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 - curt@flightgear.org
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., 675 Mass Ave, Cambridge, MA 02139, USA.
21 //
22 // $Id$
23
24 #include <simgear/compiler.h>
25
26 #ifdef SG_HAVE_STD_INCLUDES
27 #  include <cmath>
28 #else
29 #  include <math.h>
30 #endif
31 #include <string.h>
32
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/math/sg_random.h>
35
36 #include <Main/fg_props.hxx>
37
38 #include "fg_sound.hxx"
39
40
41 // static double _fg_lin(double v)   { return v; };
42 static double _fg_inv(double v)   { return (v == 0) ? 1e99 : 1/v; };
43 static double _fg_abs(double v)   { return (v >= 0) ? v : -v; };
44 static double _fg_sqrt(double v)  { return (v < 0) ? sqrt(-v) : sqrt(v); };
45 static double _fg_log10(double v) { return (v < 1) ? 0 : log10(v); };
46 static double _fg_log(double v)   { return (v < 1) ? 0 : log(v); };
47 // static double _fg_sqr(double v)   { return pow(v, 2); };
48 // static double _fg_pow3(double v)  { return pow(v, 3); };
49
50 static const struct {
51         char *name;
52         double (*fn)(double);
53 } __fg_snd_fn[] = {
54 //      {"lin", _fg_lin},
55         {"inv", _fg_inv},
56         {"abs", _fg_abs},
57         {"sqrt", _fg_sqrt},
58         {"log", _fg_log10},
59         {"ln", _fg_log},
60 //      {"sqr", _fg_sqr},
61 //      {"pow3", _fg_pow3},
62         {"", NULL}
63 };
64
65 FGSound::FGSound()
66   : _sample(NULL),
67     _active(false),
68     _condition(NULL),
69     _property(NULL),
70     _dt_play(0.0),
71     _dt_stop(0.0),
72     _prev_value(0),
73     _name(""),
74     _mode(FGSound::ONCE)    
75 {
76 }
77
78 FGSound::~FGSound()
79 {
80    if (_property)
81       delete _property;
82
83    if (_condition)
84       delete _condition;
85
86    delete _sample;
87 }
88
89 void
90 FGSound::init(SGPropertyNode *node)
91 {
92
93    //
94    // set global sound properties
95    //
96    
97    _name = node->getStringValue("name");
98    SG_LOG(SG_GENERAL, SG_INFO, "Loading sound information for: " << _name );
99
100    const char *mode_str = node->getStringValue("mode");
101    if ( !strcmp(mode_str, "looped") ) {
102        _mode = FGSound::LOOPED;
103
104    } else if ( !strcmp(mode_str, "in-transit") ) {
105        _mode = FGSound::IN_TRANSIT;
106
107    } else {
108       _mode = FGSound::ONCE;
109
110       if ( strcmp(mode_str, "") )
111          SG_LOG(SG_GENERAL,SG_INFO, "  Unknown sound mode, default to 'once'");
112    }
113
114    _property = fgGetNode(node->getStringValue("property"), true);
115    SGPropertyNode *condition = node->getChild("condition");
116    if (condition != NULL)
117       _condition = fgReadCondition(condition);
118
119    if (!_property && !_condition)
120       SG_LOG(SG_GENERAL, SG_WARN,
121              "  Neither a condition nor a property specified");
122
123    //
124    // set volume properties
125    //
126    unsigned int i;
127    float v = 0.0;
128    vector<SGPropertyNode_ptr> kids = node->getChildren("volume");
129    for (i = 0; (i < kids.size()) && (i < FGSound::MAXPROP); i++) {
130       _snd_prop volume = {NULL, NULL, NULL, 1.0, 0.0, 0.0, 0.0, false};
131
132       if (strcmp(kids[i]->getStringValue("property"), ""))
133          volume.prop = fgGetNode(kids[i]->getStringValue("property"), true);
134
135       const char *intern_str = kids[i]->getStringValue("internal");
136       if (!strcmp(intern_str, "dt_play"))
137          volume.intern = &_dt_play;
138       else if (!strcmp(intern_str, "dt_stop"))
139          volume.intern = &_dt_stop;
140       else if (!strcmp(intern_str, "random"))
141          volume.intern = &_random;
142
143       if ((volume.factor = kids[i]->getDoubleValue("factor")) != 0.0)
144          if (volume.factor < 0.0) {
145             volume.factor = -volume.factor;
146             volume.subtract = true;
147          }
148
149       const char *type_str = kids[i]->getStringValue("type");
150       if ( strcmp(type_str, "") ) {
151
152          for (int j=0; __fg_snd_fn[j].fn; j++)
153            if ( !strcmp(type_str, __fg_snd_fn[j].name) ) {
154                volume.fn = __fg_snd_fn[j].fn;
155                break;
156             }
157
158          if (!volume.fn)
159             SG_LOG(SG_GENERAL,SG_INFO,
160                    "  Unknown volume type, default to 'lin'");
161       }
162
163       volume.offset = kids[i]->getDoubleValue("offset");
164
165       if ((volume.min = kids[i]->getDoubleValue("min")) < 0.0)
166          SG_LOG( SG_GENERAL, SG_WARN,
167           "Volume minimum value below 0. Forced to 0.");
168
169       volume.max = kids[i]->getDoubleValue("max");
170       if (volume.max && (volume.max < volume.min) )
171          SG_LOG(SG_GENERAL,SG_ALERT,
172                 "  Volume maximum below minimum. Neglected.");
173
174       _volume.push_back(volume);
175       v += volume.offset;
176
177    }
178
179
180    //
181    // set pitch properties
182    //
183    float p = 0.0;
184    kids = node->getChildren("pitch");
185    for (i = 0; (i < kids.size()) && (i < FGSound::MAXPROP); i++) {
186       _snd_prop pitch = {NULL, NULL, NULL, 1.0, 1.0, 0.0, 0.0, false};
187
188       if (strcmp(kids[i]->getStringValue("property"), ""))
189          pitch.prop = fgGetNode(kids[i]->getStringValue("property"), true);
190
191       const char *intern_str = kids[i]->getStringValue("internal");
192       if (!strcmp(intern_str, "dt_play"))
193          pitch.intern = &_dt_play;
194       else if (!strcmp(intern_str, "dt_stop"))
195          pitch.intern = &_dt_stop;
196
197       if ((pitch.factor = kids[i]->getDoubleValue("factor")) != 0.0)
198          if (pitch.factor < 0.0) {
199             pitch.factor = -pitch.factor;
200             pitch.subtract = true;
201          }
202
203       const char *type_str = kids[i]->getStringValue("type");
204       if ( strcmp(type_str, "") ) {
205
206          for (int j=0; __fg_snd_fn[j].fn; j++) 
207             if ( !strcmp(type_str, __fg_snd_fn[j].name) ) {
208                pitch.fn = __fg_snd_fn[j].fn;
209                break;
210             }
211
212          if (!pitch.fn)
213             SG_LOG(SG_GENERAL,SG_INFO,
214                    "  Unknown pitch type, default to 'lin'");
215       }
216      
217       pitch.offset = kids[i]->getDoubleValue("offset");
218
219       if ((pitch.min = kids[i]->getDoubleValue("min")) < 0.0)
220          SG_LOG(SG_GENERAL,SG_WARN,
221                 "  Pitch minimum value below 0. Forced to 0.");
222
223       pitch.max = kids[i]->getDoubleValue("max");
224       if (pitch.max && (pitch.max < pitch.min) )
225          SG_LOG(SG_GENERAL,SG_ALERT,
226                 "  Pitch maximum below minimum. Neglected");
227
228       _pitch.push_back(pitch);
229       p += pitch.offset;
230    }
231
232    //
233    // Initialize the sample
234    //
235    _mgr = globals->get_soundmgr();
236    if ((_sample = _mgr->find(_name)) == NULL)
237       _sample = _mgr->add(_name, node->getStringValue("path"));
238
239    _sample->set_volume(v);
240    _sample->set_pitch(p);
241 }
242
243 void
244 FGSound::bind ()
245 {
246 }
247
248 void
249 FGSound::unbind ()
250 {
251 }
252
253 void
254 FGSound::update (double dt)
255 {
256    double curr_value = 0.0;
257
258    //
259    // If the state changes to false, stop playing.
260    //
261    if (_property)
262       curr_value = _property->getDoubleValue();
263
264    if (                                                 // Lisp, anyone?
265          (_condition && !_condition->test()) ||
266          (!_condition && _property &&
267             (
268                !curr_value ||
269                ( (_mode == FGSound::IN_TRANSIT) && (curr_value == _prev_value) )
270             )
271          )
272       )
273    {
274
275       _active = false;
276       _dt_stop += dt;
277       _dt_play = 0.0;
278
279       if (_sample->is_playing()) {
280          SG_LOG(SG_GENERAL, SG_INFO, "Stopping audio after " << _dt_play
281                                       << " sec: " << _name );
282          _sample->stop( _mgr->get_scheduler() );
283       }
284
285       return;
286
287    }
288
289    //
290    // If the mode is ONCE and the sound is still playing,
291    //  we have nothing to do anymore.
292    //
293    if (_active && (_mode == FGSound::ONCE)) {
294
295       if (!_sample->is_playing()) {
296          _dt_stop += dt;
297          _dt_play = 0.0;
298
299       } else
300          _dt_play += dt;
301
302       return;
303    }
304
305    //
306    // Update playtime, cache the current value and feed the random number
307    //
308     _dt_play += dt;
309    _prev_value = curr_value;
310    _random = sg_random();
311
312    //
313    // Update the volume
314    //
315    int i;
316    int max = _volume.size();
317    double volume = 1.0;
318    double volume_offset = 0.0;
319
320    for(i = 0; i < max; i++) {
321       double v;
322
323       if (_volume[i].prop)
324          v = _volume[i].prop->getDoubleValue();
325
326       else if (_volume[i].intern)
327          v = *_volume[i].intern;
328
329       if (_volume[i].fn)
330          v = _volume[i].fn(v);
331
332       v *= _volume[i].factor;
333
334       if (_volume[i].max && (v > _volume[i].max))
335          v = _volume[i].max;
336
337       else if (v < _volume[i].min)
338          v = _volume[i].min;
339
340       if (_volume[i].subtract)                          // Hack!
341          volume = _volume[i].offset - v;
342
343       else {
344          volume_offset += _volume[i].offset;
345          volume *= v;
346       }
347    }
348
349    //
350    // Update the pitch
351    //
352    max = _pitch.size();
353    double pitch = 1.0;
354    double pitch_offset = 0.0;
355
356    for(i = 0; i < max; i++) {
357       double p;
358
359       if (_pitch[i].prop)
360          p = _pitch[i].prop->getDoubleValue();
361
362       else if (_pitch[i].intern)
363          p = *_pitch[i].intern;
364
365       if (_pitch[i].fn)
366          p = _pitch[i].fn(p);
367
368       p *= _pitch[i].factor;
369
370       if (_pitch[i].max && (p > _pitch[i].max))
371          p = _pitch[i].max;
372
373       else if (p < _pitch[i].min)
374          p = _pitch[i].min;
375
376       if (_pitch[i].subtract)                           // Hack!
377          pitch = _pitch[i].offset - p;
378
379       else {
380          pitch_offset += _pitch[i].offset;
381          pitch *= p;
382       }
383    }
384
385    //
386    // Change sample state
387    //
388    _sample->set_pitch( pitch_offset + pitch );
389    _sample->set_volume( volume_offset + volume );
390
391
392    //
393    // Do we need to start playing the sample?
394    //
395    if (!_active) {
396
397       if (_mode == FGSound::ONCE)
398          _sample->play(_mgr->get_scheduler(), false);
399
400       else
401          _sample->play(_mgr->get_scheduler(), true);
402
403       SG_LOG(SG_GENERAL, SG_INFO, "Playing audio after " << _dt_stop 
404                                    << " sec: " << _name);
405       SG_LOG(SG_GENERAL, SG_BULK,
406                          "Playing " << ((_mode == ONCE) ? "once" : "looped"));
407
408       _active = true;
409       _dt_stop = 0.0;
410    }
411 }