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