1 /*
2 * Copyright 2011-2013 Arx Libertatis Team (see the AUTHORS file)
3 *
4 * This file is part of Arx Libertatis.
5 *
6 * Arx Libertatis is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Arx Libertatis is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with Arx Libertatis. If not, see <http://www.gnu.org/licenses/>.
18 */
19 /* Based on:
20 ===========================================================================
21 ARX FATALIS GPL Source Code
22 Copyright (C) 1999-2010 Arkane Studios SA, a ZeniMax Media company.
23
24 This file is part of the Arx Fatalis GPL Source Code ('Arx Fatalis Source Code').
25
26 Arx Fatalis Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
27 License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
28
29 Arx Fatalis Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
30 warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
31
32 You should have received a copy of the GNU General Public License along with Arx Fatalis Source Code. If not, see
33 <http://www.gnu.org/licenses/>.
34
35 In addition, the Arx Fatalis Source Code is also subject to certain additional terms. You should have received a copy of these
36 additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Arx
37 Fatalis Source Code. If not, please request a copy in writing from Arkane Studios at the address below.
38
39 If you have questions concerning this license or the applicable additional terms, you may contact in writing Arkane Studios, c/o
40 ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
41 ===========================================================================
42 */
43
44 #include "audio/Audio.h"
45
46 #include "Configure.h"
47
48 #include "audio/AudioResource.h"
49 #include "audio/Mixer.h"
50 #include "audio/Sample.h"
51 #include "audio/Ambiance.h"
52 #include "audio/AudioGlobal.h"
53 #include "audio/AudioBackend.h"
54 #include "audio/AudioSource.h"
55 #include "audio/AudioEnvironment.h"
56 #ifdef ARX_HAVE_DSOUND
57 #include "audio/dsound/DSoundBackend.h"
58 #endif
59 #ifdef ARX_HAVE_OPENAL
60 #include "audio/openal/OpenALBackend.h"
61 #endif
62
63 #include "io/log/Logger.h"
64
65 #include "platform/Lock.h"
66 #include "platform/Time.h"
67
68 using std::string;
69
70 namespace audio {
71
72 namespace {
73 static Lock * mutex = NULL;
74 }
75
init(const string & backendName,bool enableEAX)76 aalError init(const string & backendName, bool enableEAX) {
77
78 // Clean any initialized data
79 clean();
80
81 LogDebug("Init");
82
83 stream_limit_bytes = DEFAULT_STREAMLIMIT;
84
85 bool autoBackend = (backendName == "auto");
86 aalError error = AAL_ERROR_INIT;
87
88 for(int i = 0; i < 2 && !backend; i++) {
89 bool first = (i == 0);
90
91 bool matched = false;
92
93 #ifdef ARX_HAVE_OPENAL
94 if(!backend && first == (autoBackend || backendName == "OpenAL")) {
95 matched = true;
96 LogDebug("initializing OpenAL backend");
97 OpenALBackend * _backend = new OpenALBackend();
98 error = _backend->init(enableEAX);
99 if(!error) {
100 backend = _backend;
101 } else {
102 delete _backend;
103 }
104 }
105 #endif
106
107 #ifdef ARX_HAVE_DSOUND
108 if(!backend && first == (autoBackend || backendName == "DirectSound")) {
109 matched = true;
110 LogDebug("initializing DirectSound backend");
111 DSoundBackend * _backend = new DSoundBackend();
112 error = _backend->init(enableEAX);
113 if(!error) {
114 backend = _backend;
115 } else {
116 delete _backend;
117 }
118 }
119 #endif
120
121 if(first && !matched) {
122 LogError << "Unknown backend: " << backendName;
123 }
124 }
125
126 #if !defined(ARX_HAVE_OPENAL) && !defined(ARX_HAVE_DSOUND)
127 ARX_UNUSED(autoBackend), ARX_UNUSED(enableEAX);
128 #endif
129
130 if(!backend) {
131 LogError << "No working backend available";
132 return error;
133 }
134
135 mutex = new Lock();
136
137 session_time = Time::getMs();
138
139 return AAL_OK;
140 }
141
clean()142 aalError clean() {
143
144 if(!backend) {
145 return AAL_OK;
146 }
147
148 LogDebug("Clean");
149
150 _amb.clear();
151 _sample.clear();
152 _mixer.clear();
153 _env.clear();
154
155 delete backend, backend = NULL;
156
157 sample_path.clear();
158 ambiance_path.clear();
159 environment_path.clear();
160
161 delete mutex, mutex = NULL;
162
163 return AAL_OK;
164 }
165
166 #define AAL_ENTRY \
167 if(!backend) { \
168 return AAL_ERROR_INIT; \
169 } \
170 Autolock lock(mutex);
171
172 #define AAL_ENTRY_V(value) \
173 if(!backend) { \
174 return (value); \
175 } \
176 Autolock lock(mutex);
177
setStreamLimit(size_t limit)178 aalError setStreamLimit(size_t limit) {
179
180 AAL_ENTRY
181
182 stream_limit_bytes = limit;
183
184 return AAL_OK;
185 }
186
setSamplePath(const res::path & path)187 aalError setSamplePath(const res::path & path) {
188
189 AAL_ENTRY
190
191 sample_path = path;
192
193 return AAL_OK;
194 }
195
setAmbiancePath(const res::path & path)196 aalError setAmbiancePath(const res::path & path) {
197
198 AAL_ENTRY
199
200 ambiance_path = path;
201
202 return AAL_OK;
203 }
204
setEnvironmentPath(const res::path & path)205 aalError setEnvironmentPath(const res::path & path) {
206
207 AAL_ENTRY
208
209 environment_path = path;
210
211 return AAL_OK;
212 }
213
setReverbEnabled(bool enable)214 aalError setReverbEnabled(bool enable) {
215
216 AAL_ENTRY
217
218 return backend->setReverbEnabled(enable);
219 }
220
update()221 aalError update() {
222
223 AAL_ENTRY
224
225 session_time = Time::getMs();
226
227 // Update sources
228 for(Backend::source_iterator p = backend->sourcesBegin(); p != backend->sourcesEnd();) {
229 Source * source = *p;
230 if(source && (source->update(), source->isIdle())) {
231 p = backend->deleteSource(p);
232 } else {
233 ++p;
234 }
235 }
236
237 // Update ambiances
238 for(size_t i = 0; i < _amb.size(); i++) {
239 Ambiance * ambiance = _amb[i];
240 if(ambiance) {
241 ambiance->update();
242 if(ambiance->getChannel().flags & FLAG_AUTOFREE && ambiance->isIdle()) {
243 _amb.remove(i);
244 }
245 }
246 }
247
248 // Update samples
249 for(size_t i = 0; i < _sample.size(); i++) {
250 Sample * sample = _sample[i];
251 if(sample && sample->isReferenced() < 1) {
252 _sample.remove(i);
253 }
254 }
255
256 return backend->updateDeferred();
257 }
258
259 // Resource creation
260
createMixer()261 MixerId createMixer() {
262
263 AAL_ENTRY_V(INVALID_ID)
264
265 Mixer * mixer = new Mixer();
266
267 MixerId id = _mixer.add(mixer);
268 if(id == INVALID_ID) {
269 delete mixer;
270 }
271
272 return id;
273 }
274
createSample(const res::path & name)275 SampleId createSample(const res::path & name) {
276
277 AAL_ENTRY_V(INVALID_ID)
278
279 Sample * sample = new Sample(name);
280
281 SampleId s_id = INVALID_ID;
282 if(sample->load() || (s_id = _sample.add(sample)) == INVALID_ID) {
283 delete sample;
284 } else {
285 sample->reference();
286 }
287
288 return Backend::clearSource(s_id);
289 }
290
createAmbiance(const res::path & name)291 AmbianceId createAmbiance(const res::path & name) {
292
293 AAL_ENTRY_V(INVALID_ID)
294
295 Ambiance * ambiance = new Ambiance(name);
296 AmbianceId a_id = INVALID_ID;
297 if(ambiance->load() || (a_id = _amb.add(ambiance)) == INVALID_ID) {
298 delete ambiance;
299 LogError << "Ambiance " << name << " not found";
300 }
301
302 return a_id;
303 }
304
createEnvironment(const res::path & name)305 EnvId createEnvironment(const res::path & name) {
306
307 AAL_ENTRY_V(INVALID_ID)
308
309 Environment * env = new Environment(name);
310 EnvId e_id = INVALID_ID;
311 if(env->load() || (e_id = _env.add(env)) == INVALID_ID) {
312 delete env;
313 LogError << "Environment " << name << " not found";
314 }
315
316 return e_id;
317 }
318
319 // Resource destruction
320
deleteSample(SampleId sample_id)321 aalError deleteSample(SampleId sample_id) {
322
323 AAL_ENTRY
324
325 SampleId s_id = Backend::getSampleId(sample_id);
326 if(!_sample.isValid(s_id)) {
327 return AAL_ERROR_HANDLE;
328 }
329
330 _sample.remove(s_id);
331
332 return AAL_OK;
333 }
334
deleteAmbiance(AmbianceId a_id)335 aalError deleteAmbiance(AmbianceId a_id) {
336
337 AAL_ENTRY
338
339 _amb.remove(a_id);
340
341 return AAL_OK;
342 }
343
getAmbiance(const res::path & name)344 AmbianceId getAmbiance(const res::path & name) {
345
346 AAL_ENTRY_V(INVALID_ID)
347
348 for(size_t i = 0; i < _amb.size(); i++) {
349 if(_amb[i] && name == _amb[i]->getName()) {
350 return i;
351 }
352 }
353
354 return INVALID_ID;
355 }
356
getEnvironment(const res::path & name)357 EnvId getEnvironment(const res::path & name) {
358
359 AAL_ENTRY_V(INVALID_ID)
360
361 for(size_t i = 0; i < _env.size(); i++) {
362 if(_env[i] && name == _env[i]->name) {
363 return i;
364 }
365 }
366
367 return INVALID_ID;
368 }
369
370 // Retrieve next resource by ID
371
getNextAmbiance(AmbianceId ambiance_id)372 AmbianceId getNextAmbiance(AmbianceId ambiance_id) {
373
374 AAL_ENTRY_V(INVALID_ID)
375
376 size_t i = _amb.isValid(ambiance_id) ? ambiance_id + 1 : 0;
377
378 for(; i < _amb.size(); i++) {
379 if(_amb[i]) {
380 return i;
381 }
382 }
383
384 return INVALID_ID;
385 }
386
387 // Environment setup
388
setRoomRolloffFactor(float factor)389 aalError setRoomRolloffFactor(float factor) {
390
391 AAL_ENTRY
392
393 LogDebug("SetRoomRolloffFactor " << factor);
394
395 return backend->setRoomRolloffFactor(factor);
396 }
397
398 // Listener settings
399
setUnitFactor(float factor)400 aalError setUnitFactor(float factor) {
401
402 AAL_ENTRY
403
404 LogDebug("SetUnitFactor " << factor);
405
406 return backend->setUnitFactor(factor);
407 }
408
setRolloffFactor(float factor)409 aalError setRolloffFactor(float factor) {
410
411 AAL_ENTRY
412
413 LogDebug("SetRolloffFactor " << factor);
414
415 return backend->setRolloffFactor(factor);
416 }
417
setListenerPosition(const Vec3f & position)418 aalError setListenerPosition(const Vec3f & position) {
419
420 AAL_ENTRY
421
422 return backend->setListenerPosition(position);
423 }
424
setListenerDirection(const Vec3f & front,const Vec3f & up)425 aalError setListenerDirection(const Vec3f & front, const Vec3f & up) {
426
427 AAL_ENTRY
428
429 return backend->setListenerOrientation(front, up);
430 }
431
setListenerEnvironment(EnvId e_id)432 aalError setListenerEnvironment(EnvId e_id) {
433
434 AAL_ENTRY
435
436 if(!_env.isValid(e_id)) {
437 return AAL_ERROR_HANDLE;
438 }
439
440 LogDebug("SetListenerEnvironment " << _env[e_id]->name);
441
442 return backend->setListenerEnvironment(*_env[e_id]);
443 }
444
445 // Mixer setup
446
setMixerVolume(MixerId m_id,float volume)447 aalError setMixerVolume(MixerId m_id, float volume) {
448
449 AAL_ENTRY
450
451 if(!_mixer.isValid(m_id)) {
452 return AAL_ERROR_HANDLE;
453 }
454
455 LogDebug("SetMixerVolume " << m_id << " volume=" << volume);
456
457 return _mixer[m_id]->setVolume(volume);
458 }
459
setMixerParent(MixerId m_id,MixerId pm_id)460 aalError setMixerParent(MixerId m_id, MixerId pm_id) {
461
462 AAL_ENTRY
463
464 if(m_id == pm_id || !_mixer.isValid(m_id) || !_mixer.isValid(pm_id)) {
465 return AAL_ERROR_HANDLE;
466 }
467
468 LogDebug("SetMixerParent " << m_id << " parent=" << pm_id);
469
470 return _mixer[m_id]->setParent(_mixer[pm_id]);
471 }
472
473 // Mixer status
474
getMixerVolume(MixerId m_id,float * volume)475 aalError getMixerVolume(MixerId m_id, float * volume) {
476
477 *volume = DEFAULT_VOLUME;
478
479 AAL_ENTRY
480
481 if(!_mixer.isValid(m_id)) {
482 return AAL_ERROR_HANDLE;
483 }
484
485 *volume = _mixer[m_id]->getVolume();
486
487 return AAL_OK;
488 }
489
490 // Mixer control
491
mixerStop(MixerId m_id)492 aalError mixerStop(MixerId m_id) {
493
494 AAL_ENTRY
495
496 if(!_mixer.isValid(m_id)) {
497 return AAL_ERROR_HANDLE;
498 }
499
500 LogDebug("MixerStop " << m_id);
501
502 return _mixer[m_id]->stop();
503 }
504
mixerPause(MixerId m_id)505 aalError mixerPause(MixerId m_id) {
506
507 AAL_ENTRY;
508
509 if(!_mixer.isValid(m_id)) {
510 return AAL_ERROR_HANDLE;
511 }
512
513 LogDebug("MixerPause " << m_id);
514
515 return _mixer[m_id]->pause();
516 }
517
mixerResume(MixerId m_id)518 aalError mixerResume(MixerId m_id) {
519
520 AAL_ENTRY
521
522 if(!_mixer.isValid(m_id)) {
523 return AAL_ERROR_HANDLE;
524 }
525
526 LogDebug("MixerResume " << m_id);
527
528 return _mixer[m_id]->resume();
529 }
530
531 // Sample setup
532
setSampleVolume(SourceId sample_id,float volume)533 aalError setSampleVolume(SourceId sample_id, float volume) {
534
535 AAL_ENTRY
536
537 Source * source = backend->getSource(sample_id);
538 if(!source) {
539 return AAL_ERROR_HANDLE;
540 }
541
542 return source->setVolume(volume);
543 }
544
setSamplePitch(SourceId sample_id,float pitch)545 aalError setSamplePitch(SourceId sample_id, float pitch) {
546
547 AAL_ENTRY
548
549 Source * source = backend->getSource(sample_id);
550 if(!source) {
551 return AAL_ERROR_HANDLE;
552 }
553
554 return source->setPitch(pitch);
555 }
556
setSamplePosition(SourceId sample_id,const Vec3f & position)557 aalError setSamplePosition(SourceId sample_id, const Vec3f & position) {
558
559 AAL_ENTRY
560
561 Source * source = backend->getSource(sample_id);
562 if(!source) {
563 return AAL_ERROR_HANDLE;
564 }
565
566 return source->setPosition(position);
567 }
568
569 // Sample status
570
getSampleName(SampleId sample_id,res::path & name)571 aalError getSampleName(SampleId sample_id, res::path & name) {
572
573 name.clear();
574
575 AAL_ENTRY
576
577 SampleId s_id = Backend::getSampleId(sample_id);
578 if(!_sample.isValid(s_id)) {
579 return AAL_ERROR_HANDLE;
580 }
581
582 name = _sample[s_id]->getName();
583
584 return AAL_OK;
585 }
586
getSampleLength(SampleId sample_id,size_t & length,TimeUnit unit)587 aalError getSampleLength(SampleId sample_id, size_t & length, TimeUnit unit) {
588
589 length = 0;
590
591 AAL_ENTRY
592
593 SampleId s_id = Backend::getSampleId(sample_id);
594 if(!_sample.isValid(s_id)) {
595 return AAL_ERROR_HANDLE;
596 }
597
598 Sample * sample = _sample[s_id];
599 length = bytesToUnits(sample->getLength(), sample->getFormat(), unit);
600
601 return AAL_OK;
602 }
603
isSamplePlaying(SourceId sample_id)604 bool isSamplePlaying(SourceId sample_id) {
605
606 AAL_ENTRY_V(false)
607
608 Source * source = backend->getSource(sample_id);
609 if(!source) {
610 return false;
611 }
612
613 bool ret = source->isPlaying();
614
615 return ret;
616 }
617
618 // Sample control
619
samplePlay(SampleId & sample_id,const Channel & channel,unsigned play_count)620 aalError samplePlay(SampleId & sample_id, const Channel & channel, unsigned play_count) {
621
622 AAL_ENTRY
623
624 SampleId s_id = Backend::getSampleId(sample_id);
625 sample_id = Backend::clearSource(sample_id);
626 if(!_sample.isValid(s_id) || !_mixer.isValid(channel.mixer)) {
627 return AAL_ERROR_HANDLE;
628 }
629
630 LogDebug("SamplePlay " << _sample[s_id]->getName() << " play_count=" << play_count);
631
632 Source * source = backend->getSource(sample_id);
633 if(source) {
634 if(channel.flags == source->getChannel().flags) {
635 source = NULL;
636 } else if(channel.flags & FLAG_RESTART) {
637 source->stop();
638 } else if(channel.flags & FLAG_ENQUEUE) {
639 source->play(play_count);
640 } else if(source->isIdle()) {
641 source->setMixer(channel.mixer);
642 source->setVolume(channel.volume);
643 source->setPitch(channel.pitch);
644 source->setPan(channel.pan);
645 source->setPosition(channel.position);
646 source->setVelocity(channel.velocity);
647 source->setDirection(channel.direction);
648 source->setCone(channel.cone);
649 source->setFalloff(channel.falloff);
650 } else {
651 source = NULL;
652 }
653 }
654
655 if(!source) {
656 source = backend->createSource(s_id, channel);
657 if(!source) {
658 return AAL_ERROR_SYSTEM;
659 }
660 }
661
662 backend->updateDeferred();
663
664 if(aalError error = source->play(play_count)) {
665 return error;
666 }
667
668 sample_id = source->getId();
669
670 if(channel.flags & FLAG_AUTOFREE) {
671 _sample[s_id]->dereference();
672 }
673
674 return AAL_OK;
675 }
676
sampleStop(SourceId & sample_id)677 aalError sampleStop(SourceId & sample_id) {
678
679 AAL_ENTRY
680
681 Source * source = backend->getSource(sample_id);
682 if(!source) {
683 return AAL_ERROR_HANDLE;
684 }
685
686 LogDebug("SampleStop " << source->getSample()->getName());
687
688 sample_id = Backend::clearSource(sample_id);
689
690 return source->stop();
691 }
692
693 // Track setup
694
muteAmbianceTrack(AmbianceId a_id,const string & track,bool mute)695 aalError muteAmbianceTrack(AmbianceId a_id, const string & track, bool mute) {
696
697 AAL_ENTRY
698
699 if(!_amb.isValid(a_id)) {
700 return AAL_ERROR_HANDLE;
701 }
702
703 LogDebug("MuteAmbianceTrack " << _amb[a_id]->getName() << " " << track << " " << mute);
704
705 return _amb[a_id]->muteTrack(track, mute);
706 }
707
708 // Ambiance setup
709
setAmbianceUserData(AmbianceId a_id,void * data)710 aalError setAmbianceUserData(AmbianceId a_id, void * data) {
711
712 AAL_ENTRY
713
714 if(!_amb.isValid(a_id)) {
715 return AAL_ERROR_HANDLE;
716 }
717
718 LogDebug("SetAmbianceUserData " << _amb[a_id]->getName() << " " << data);
719
720 _amb[a_id]->setUserData(data);
721
722 return AAL_OK;
723 }
724
setAmbianceVolume(AmbianceId a_id,float volume)725 aalError setAmbianceVolume(AmbianceId a_id, float volume) {
726
727 AAL_ENTRY
728
729 if(!_amb.isValid(a_id)) {
730 return AAL_ERROR_HANDLE;
731 }
732
733 LogDebug("SetAmbianceVolume " << _amb[a_id]->getName() << " " << volume);
734
735 return _amb[a_id]->setVolume(volume);
736 }
737
738 // Ambiance status
739
getAmbianceName(AmbianceId a_id,res::path & name)740 aalError getAmbianceName(AmbianceId a_id, res::path & name) {
741
742 name.clear();
743
744 AAL_ENTRY
745
746 if(!_amb.isValid(a_id)) {
747 return AAL_ERROR_HANDLE;
748 }
749
750 name = _amb[a_id]->getName();
751
752 return AAL_OK;
753 }
754
getAmbianceUserData(AmbianceId a_id,void ** data)755 aalError getAmbianceUserData(AmbianceId a_id, void ** data) {
756
757 AAL_ENTRY
758
759 if(!_amb.isValid(a_id)) {
760 return AAL_ERROR_HANDLE;
761 }
762
763 *data = _amb[a_id]->getUserData();
764
765 return AAL_OK;
766 }
767
getAmbianceVolume(AmbianceId a_id,float & _volume)768 aalError getAmbianceVolume(AmbianceId a_id, float & _volume) {
769
770 _volume = DEFAULT_VOLUME;
771
772 AAL_ENTRY
773
774 if(!_amb.isValid(a_id)) {
775 return AAL_ERROR_HANDLE;
776 }
777
778 if(!(_amb[a_id]->getChannel().flags & FLAG_VOLUME)) {
779 return AAL_ERROR_INIT;
780 }
781
782 _volume = _amb[a_id]->getChannel().volume;
783
784 return AAL_OK;
785 }
786
isAmbianceLooped(AmbianceId a_id)787 bool isAmbianceLooped(AmbianceId a_id) {
788
789 AAL_ENTRY_V(false)
790
791 if(!_amb.isValid(a_id)) {
792 return false;
793 }
794
795 return _amb[a_id]->isLooped();
796 }
797
798 // Ambiance control
799
ambiancePlay(AmbianceId a_id,const Channel & channel,bool loop,size_t fade_interval)800 aalError ambiancePlay(AmbianceId a_id, const Channel & channel, bool loop, size_t fade_interval) {
801
802 AAL_ENTRY
803
804 if(!_amb.isValid(a_id) || !_mixer.isValid(channel.mixer)) {
805 return AAL_ERROR_HANDLE;
806 }
807
808 LogDebug("AmbiancePlay " << _amb[a_id]->getName() << " loop=" << loop << " fade=" << fade_interval);
809
810 return _amb[a_id]->play(channel, loop, fade_interval);
811 }
812
ambianceStop(AmbianceId a_id,size_t fade_interval)813 aalError ambianceStop(AmbianceId a_id, size_t fade_interval) {
814
815 AAL_ENTRY
816
817 if(!_amb.isValid(a_id)) {
818 return AAL_ERROR_HANDLE;
819 }
820
821 LogDebug("AmbianceStop " << _amb[a_id]->getName() << " " << fade_interval);
822
823 _amb[a_id]->stop(fade_interval);
824
825 return AAL_OK;
826 }
827
828 } // namespace audio
829