Audaspace:
[blender.git] / source / gameengine / Ketsji / KX_SoundActuator.cpp
1 /*
2  * KX_SoundActuator.cpp
3  *
4  *
5  * ***** BEGIN GPL LICENSE BLOCK *****
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
22  * All rights reserved.
23  *
24  * The Original Code is: all of this file.
25  *
26  * Contributor(s): none yet.
27  *
28  * ***** END GPL LICENSE BLOCK *****
29  *
30  */
31
32 /** \file gameengine/Ketsji/KX_SoundActuator.cpp
33  *  \ingroup ketsji
34  */
35
36
37 #include "KX_SoundActuator.h"
38
39 #ifdef WITH_AUDASPACE
40 #  include "AUD_C-API.h"
41 #  include "AUD_PingPongFactory.h"
42 #  include "AUD_IDevice.h"
43 #  include "AUD_I3DHandle.h"
44 #endif
45
46 #include "KX_GameObject.h"
47 #include "KX_PyMath.h" // needed for PyObjectFrom()
48 #include "KX_PythonInit.h"
49 #include "KX_Camera.h"
50 #include <iostream>
51
52 /* ------------------------------------------------------------------------- */
53 /* Native functions                                                          */
54 /* ------------------------------------------------------------------------- */
55 KX_SoundActuator::KX_SoundActuator(SCA_IObject* gameobj,
56                                                                    boost::shared_ptr<AUD_IFactory> sound,
57                                                                    float volume,
58                                                                    float pitch,
59                                                                    bool is3d,
60                                                                    KX_3DSoundSettings settings,
61                                                                    KX_SOUNDACT_TYPE type)//,
62                                                                    : SCA_IActuator(gameobj, KX_ACT_SOUND)
63 {
64         m_sound = sound;
65         m_volume = volume;
66         m_pitch = pitch;
67         m_is3d = is3d;
68         m_3d = settings;
69         m_type = type;
70         m_isplaying = false;
71 }
72
73
74
75 KX_SoundActuator::~KX_SoundActuator()
76 {
77         if (m_handle.get())
78                 m_handle->stop();
79 }
80
81 void KX_SoundActuator::play()
82 {
83         if (m_handle.get())
84                 m_handle->stop();
85
86         if (!m_sound.get())
87                 return;
88
89         // this is the sound that will be played and not deleted afterwards
90         boost::shared_ptr<AUD_IFactory> sound = m_sound;
91
92         bool loop = false;
93
94         switch (m_type)
95         {
96         case KX_SOUNDACT_LOOPBIDIRECTIONAL:
97         case KX_SOUNDACT_LOOPBIDIRECTIONAL_STOP:
98                 sound = boost::shared_ptr<AUD_IFactory>(new AUD_PingPongFactory(sound));
99                 // fall through
100         case KX_SOUNDACT_LOOPEND:
101         case KX_SOUNDACT_LOOPSTOP:
102                 loop = true;
103                 break;
104         case KX_SOUNDACT_PLAYSTOP:
105         case KX_SOUNDACT_PLAYEND:
106         default:
107                 break;
108         }
109
110         try
111         {
112                 m_handle = AUD_getDevice()->play(sound, 0);
113         }
114         catch(AUD_Exception&)
115         {
116                 // cannot play back, ignore
117                 return;
118         }
119
120         boost::shared_ptr<AUD_I3DHandle> handle3d = boost::dynamic_pointer_cast<AUD_I3DHandle>(m_handle);
121
122         if (m_is3d && handle3d.get())
123         {
124                 handle3d->setRelative(true);
125                 handle3d->setVolumeMaximum(m_3d.max_gain);
126                 handle3d->setVolumeMinimum(m_3d.min_gain);
127                 handle3d->setDistanceReference(m_3d.reference_distance);
128                 handle3d->setDistanceMaximum(m_3d.max_distance);
129                 handle3d->setAttenuation(m_3d.rolloff_factor);
130                 handle3d->setConeAngleInner(m_3d.cone_inner_angle);
131                 handle3d->setConeAngleOuter(m_3d.cone_outer_angle);
132                 handle3d->setConeVolumeOuter(m_3d.cone_outer_gain);
133         }
134
135         if (loop)
136                 m_handle->setLoopCount(-1);
137         m_handle->setPitch(m_pitch);
138         m_handle->setVolume(m_volume);
139         m_isplaying = true;
140 }
141
142 CValue* KX_SoundActuator::GetReplica()
143 {
144         KX_SoundActuator* replica = new KX_SoundActuator(*this);
145         replica->ProcessReplica();
146         return replica;
147 };
148
149 void KX_SoundActuator::ProcessReplica()
150 {
151         SCA_IActuator::ProcessReplica();
152         m_handle = boost::shared_ptr<AUD_IHandle>();
153 }
154
155 bool KX_SoundActuator::Update(double curtime, bool frame)
156 {
157         if (!frame)
158                 return true;
159         bool result = false;
160
161         // do nothing on negative events, otherwise sounds are played twice!
162         bool bNegativeEvent = IsNegativeEvent();
163         bool bPositiveEvent = m_posevent;
164         
165         RemoveAllEvents();
166
167         if (!m_sound.get())
168                 return false;
169
170         // actual audio device playing state
171         bool isplaying = m_handle.get() ? (m_handle->getStatus() == AUD_STATUS_PLAYING) : false;
172
173         if (bNegativeEvent)
174         {
175                 // here must be a check if it is still playing
176                 if (m_isplaying && isplaying)
177                 {
178                         switch (m_type)
179                         {
180                         case KX_SOUNDACT_PLAYSTOP:
181                         case KX_SOUNDACT_LOOPSTOP:
182                         case KX_SOUNDACT_LOOPBIDIRECTIONAL_STOP:
183                                 {
184                                         // stop immediately
185                                         if (m_handle.get())
186                                                 m_handle->stop();
187                                         m_handle = boost::shared_ptr<AUD_IHandle>();
188                                         break;
189                                 }
190                         case KX_SOUNDACT_PLAYEND:
191                                 {
192                                         // do nothing, sound will stop anyway when it's finished
193                                         break;
194                                 }
195                         case KX_SOUNDACT_LOOPEND:
196                         case KX_SOUNDACT_LOOPBIDIRECTIONAL:
197                                 {
198                                         // stop the looping so that the sound stops when it finished
199                                         if (m_handle.get())
200                                                 m_handle->setLoopCount(0);
201                                         break;
202                                 }
203                         default:
204                                 // implement me !!
205                                 break;
206                         }
207                 }
208                 // remember that we tried to stop the actuator
209                 m_isplaying = false;
210         }
211         
212 #if 1
213         // Warning: when de-activating the actuator, after a single negative event this runs again with...
214         // m_posevent==false && m_posevent==false, in this case IsNegativeEvent() returns false 
215         // and assumes this is a positive event.
216         // check that we actually have a positive event so as not to play sounds when being disabled.
217         else if (bPositiveEvent) { // <- added since 2.49
218 #else
219         else {  // <- works in most cases except a loop-end sound will never stop unless
220                         // the negative pulse is done continuesly
221 #endif
222                 if (!m_isplaying)
223                         play();
224         }
225         // verify that the sound is still playing
226         isplaying = m_handle.get() ? (m_handle->getStatus() == AUD_STATUS_PLAYING) : false;
227
228         if (isplaying)
229         {
230                 boost::shared_ptr<AUD_I3DHandle> handle3d = boost::dynamic_pointer_cast<AUD_I3DHandle>(m_handle);
231
232                 if (m_is3d && handle3d.get())
233                 {
234                         KX_Camera* cam = KX_GetActiveScene()->GetActiveCamera();
235                         if (cam)
236                         {
237                                 KX_GameObject* obj = (KX_GameObject*)this->GetParent();
238                                 MT_Point3 p;
239                                 MT_Matrix3x3 Mo;
240                                 AUD_Vector3 v;
241                                 float q[4];
242
243                                 Mo = cam->NodeGetWorldOrientation().inverse();
244                                 p = (obj->NodeGetWorldPosition() - cam->NodeGetWorldPosition());
245                                 p = Mo * p;
246                                 p.getValue(v.get());
247                                 handle3d->setSourceLocation(v);
248                                 p = (obj->GetLinearVelocity() - cam->GetLinearVelocity());
249                                 p = Mo * p;
250                                 p.getValue(v.get());
251                                 handle3d->setSourceVelocity(v);
252                                 (Mo * obj->NodeGetWorldOrientation()).getRotation().getValue(q);
253                                 handle3d->setSourceOrientation(AUD_Quaternion(q[3], q[0], q[1], q[2]));
254                         }
255                 }
256                 result = true;
257         }
258         else
259         {
260                 m_isplaying = false;
261                 result = false;
262         }
263         return result;
264 }
265
266 #ifdef WITH_PYTHON
267
268 /* ------------------------------------------------------------------------- */
269 /* Python functions                                                          */
270 /* ------------------------------------------------------------------------- */
271
272
273
274 /* Integration hooks ------------------------------------------------------- */
275 PyTypeObject KX_SoundActuator::Type = {
276         PyVarObject_HEAD_INIT(NULL, 0)
277         "KX_SoundActuator",
278         sizeof(PyObjectPlus_Proxy),
279         0,
280         py_base_dealloc,
281         0,
282         0,
283         0,
284         0,
285         py_base_repr,
286         0,0,0,0,0,0,0,0,0,
287         Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
288         0,0,0,0,0,0,0,
289         Methods,
290         0,
291         0,
292         &SCA_IActuator::Type,
293         0,0,0,0,0,0,
294         py_base_new
295 };
296
297 PyMethodDef KX_SoundActuator::Methods[] = {
298         KX_PYMETHODTABLE_NOARGS(KX_SoundActuator, startSound),
299         KX_PYMETHODTABLE_NOARGS(KX_SoundActuator, pauseSound),
300         KX_PYMETHODTABLE_NOARGS(KX_SoundActuator, stopSound),
301         {NULL, NULL} //Sentinel
302 };
303
304 PyAttributeDef KX_SoundActuator::Attributes[] = {
305         KX_PYATTRIBUTE_BOOL_RO("is3D", KX_SoundActuator, m_is3d),
306         KX_PYATTRIBUTE_RW_FUNCTION("volume_maximum", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
307         KX_PYATTRIBUTE_RW_FUNCTION("volume_minimum", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
308         KX_PYATTRIBUTE_RW_FUNCTION("distance_reference", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
309         KX_PYATTRIBUTE_RW_FUNCTION("distance_maximum", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
310         KX_PYATTRIBUTE_RW_FUNCTION("attenuation", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
311         KX_PYATTRIBUTE_RW_FUNCTION("cone_angle_inner", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
312         KX_PYATTRIBUTE_RW_FUNCTION("cone_angle_outer", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
313         KX_PYATTRIBUTE_RW_FUNCTION("cone_volume_outer", KX_SoundActuator, pyattr_get_3d_property, pyattr_set_3d_property),
314         KX_PYATTRIBUTE_RW_FUNCTION("sound", KX_SoundActuator, pyattr_get_sound, pyattr_set_sound),
315
316         KX_PYATTRIBUTE_RW_FUNCTION("time", KX_SoundActuator, pyattr_get_audposition, pyattr_set_audposition),
317         KX_PYATTRIBUTE_RW_FUNCTION("volume", KX_SoundActuator, pyattr_get_gain, pyattr_set_gain),
318         KX_PYATTRIBUTE_RW_FUNCTION("pitch", KX_SoundActuator, pyattr_get_pitch, pyattr_set_pitch),
319         KX_PYATTRIBUTE_ENUM_RW("mode",KX_SoundActuator::KX_SOUNDACT_NODEF+1,KX_SoundActuator::KX_SOUNDACT_MAX-1,false,KX_SoundActuator,m_type),
320         { NULL }        //Sentinel
321 };
322
323 /* Methods ----------------------------------------------------------------- */
324 KX_PYMETHODDEF_DOC_NOARGS(KX_SoundActuator, startSound,
325 "startSound()\n"
326 "\tStarts the sound.\n")
327 {
328         switch (m_handle.get() ? m_handle->getStatus() : AUD_STATUS_INVALID) {
329                 case AUD_STATUS_PLAYING:
330                         break;
331                 case AUD_STATUS_PAUSED:
332                         m_handle->resume();
333                         break;
334                 default:
335                         play();
336         }
337         Py_RETURN_NONE;
338 }
339
340 KX_PYMETHODDEF_DOC_NOARGS(KX_SoundActuator, pauseSound,
341 "pauseSound()\n"
342 "\tPauses the sound.\n")
343 {
344         if (m_handle.get())
345                 m_handle->pause();
346         Py_RETURN_NONE;
347 }
348
349 KX_PYMETHODDEF_DOC_NOARGS(KX_SoundActuator, stopSound,
350 "stopSound()\n"
351 "\tStops the sound.\n")
352 {
353         if (m_handle.get())
354                 m_handle->stop();
355         m_handle = boost::shared_ptr<AUD_IHandle>();
356         Py_RETURN_NONE;
357 }
358
359 /* Atribute setting and getting -------------------------------------------- */
360 PyObject *KX_SoundActuator::pyattr_get_3d_property(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef)
361 {
362         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
363         const char* prop = attrdef->m_name;
364         float result_value = 0.0;
365
366         if (!strcmp(prop, "volume_maximum")) {
367                 result_value = actuator->m_3d.max_gain;
368
369         } else if (!strcmp(prop, "volume_minimum")) {
370                 result_value = actuator->m_3d.min_gain;
371
372         } else if (!strcmp(prop, "distance_reference")) {
373                 result_value = actuator->m_3d.reference_distance;
374
375         } else if (!strcmp(prop, "distance_maximum")) {
376                 result_value = actuator->m_3d.max_distance;
377
378         } else if (!strcmp(prop, "attenuation")) {
379                 result_value = actuator->m_3d.rolloff_factor;
380
381         } else if (!strcmp(prop, "cone_angle_inner")) {
382                 result_value = actuator->m_3d.cone_inner_angle;
383
384         } else if (!strcmp(prop, "cone_angle_outer")) {
385                 result_value = actuator->m_3d.cone_outer_angle;
386
387         } else if (!strcmp(prop, "cone_volume_outer")) {
388                 result_value = actuator->m_3d.cone_outer_gain;
389
390         } else {
391                 Py_RETURN_NONE;
392         }
393
394         PyObject *result = PyFloat_FromDouble(result_value);
395         return result;
396 }
397
398 PyObject *KX_SoundActuator::pyattr_get_audposition(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef)
399 {
400         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
401         float position = 0.0;
402
403         if (actuator->m_handle.get())
404                 position = actuator->m_handle->getPosition();
405
406         PyObject *result = PyFloat_FromDouble(position);
407
408         return result;
409 }
410
411 PyObject *KX_SoundActuator::pyattr_get_gain(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef)
412 {
413         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
414         float gain = actuator->m_volume;
415
416         PyObject *result = PyFloat_FromDouble(gain);
417
418         return result;
419 }
420
421 PyObject *KX_SoundActuator::pyattr_get_pitch(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef)
422 {
423         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
424         float pitch = actuator->m_pitch;
425
426         PyObject *result = PyFloat_FromDouble(pitch);
427
428         return result;
429 }
430
431 PyObject *KX_SoundActuator::pyattr_get_sound(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef)
432 {
433         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
434         if (actuator->m_sound.get())
435                 return (PyObject *)AUD_getPythonFactory(&actuator->m_sound);
436         else
437                 Py_RETURN_NONE;
438 }
439
440 int KX_SoundActuator::pyattr_set_3d_property(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
441 {
442         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
443         const char* prop = attrdef->m_name;
444         float prop_value = 0.0;
445
446         if (!PyArg_Parse(value, "f", &prop_value))
447                 return PY_SET_ATTR_FAIL;
448
449         boost::shared_ptr<AUD_I3DHandle> handle3d = boost::dynamic_pointer_cast<AUD_I3DHandle>(actuator->m_handle);
450         // if sound is working and 3D, set the new setting
451         if (!actuator->m_is3d)
452                 return PY_SET_ATTR_FAIL;
453
454         if (!strcmp(prop, "volume_maximum")) {
455                 actuator->m_3d.max_gain = prop_value;
456                 if (handle3d.get())
457                         handle3d->setVolumeMaximum(prop_value);
458
459         } else if (!strcmp(prop, "volume_minimum")) {
460                 actuator->m_3d.min_gain = prop_value;
461                 if (handle3d.get())
462                         handle3d->setVolumeMinimum(prop_value);
463
464         } else if (!strcmp(prop, "distance_reference")) {
465                 actuator->m_3d.reference_distance = prop_value;
466                 if (handle3d.get())
467                         handle3d->setDistanceReference(prop_value);
468
469         } else if (!strcmp(prop, "distance_maximum")) {
470                 actuator->m_3d.max_distance = prop_value;
471                 if (handle3d.get())
472                         handle3d->setDistanceMaximum(prop_value);
473
474         } else if (!strcmp(prop, "attenuation")) {
475                 actuator->m_3d.rolloff_factor = prop_value;
476                 if (handle3d.get())
477                         handle3d->setAttenuation(prop_value);
478
479         } else if (!!strcmp(prop, "cone_angle_inner")) {
480                 actuator->m_3d.cone_inner_angle = prop_value;
481                 if (handle3d.get())
482                         handle3d->setConeAngleInner(prop_value);
483
484         } else if (!strcmp(prop, "cone_angle_outer")) {
485                 actuator->m_3d.cone_outer_angle = prop_value;
486                 if (handle3d.get())
487                         handle3d->setConeAngleOuter(prop_value);
488
489         } else if (!strcmp(prop, "cone_volume_outer")) {
490                 actuator->m_3d.cone_outer_gain = prop_value;
491                 if (handle3d.get())
492                         handle3d->setConeVolumeOuter(prop_value);
493
494         } else {
495                 return PY_SET_ATTR_FAIL;
496         }
497         
498         return PY_SET_ATTR_SUCCESS;
499 }
500
501 int KX_SoundActuator::pyattr_set_audposition(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
502 {
503         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
504
505         float position = 1.0;
506         if (!PyArg_Parse(value, "f", &position))
507                 return PY_SET_ATTR_FAIL;
508
509         if (actuator->m_handle.get())
510                 actuator->m_handle->seek(position);
511         return PY_SET_ATTR_SUCCESS;
512 }
513
514 int KX_SoundActuator::pyattr_set_gain(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
515 {
516         float gain = 1.0;
517         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
518         if (!PyArg_Parse(value, "f", &gain))
519                 return PY_SET_ATTR_FAIL;
520
521         actuator->m_volume = gain;
522         if (actuator->m_handle.get())
523                 actuator->m_handle->setVolume(gain);
524
525         return PY_SET_ATTR_SUCCESS;
526 }
527
528 int KX_SoundActuator::pyattr_set_pitch(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
529 {
530         float pitch = 1.0;
531         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
532         if (!PyArg_Parse(value, "f", &pitch))
533                 return PY_SET_ATTR_FAIL;
534
535         actuator->m_pitch = pitch;
536         if (actuator->m_handle.get())
537                 actuator->m_handle->setPitch(pitch);
538
539         return PY_SET_ATTR_SUCCESS;
540 }
541
542 int KX_SoundActuator::pyattr_set_sound(void *self, const struct KX_PYATTRIBUTE_DEF *attrdef, PyObject *value)
543 {
544         PyObject *sound = NULL;
545         KX_SoundActuator * actuator = static_cast<KX_SoundActuator *> (self);
546         if (!PyArg_Parse(value, "O", &sound))
547                 return PY_SET_ATTR_FAIL;
548
549         boost::shared_ptr<AUD_IFactory>* snd = reinterpret_cast<boost::shared_ptr<AUD_IFactory>*>(AUD_getPythonSound((void *)sound));
550         if (snd)
551         {
552                 actuator->m_sound = *snd;
553                 delete snd;
554                 return PY_SET_ATTR_SUCCESS;
555         }
556
557         return PY_SET_ATTR_FAIL;
558 }
559
560 #endif // WITH_PYTHON