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
20 #include "audio/openal/OpenALBackend.h"
21
22 #include <stddef.h>
23 #include <cstring>
24
25 #include <boost/math/special_functions/fpclassify.hpp>
26
27 #include "audio/openal/OpenALSource.h"
28 #include "audio/openal/OpenALUtils.h"
29 #include "audio/AudioEnvironment.h"
30 #include "audio/AudioGlobal.h"
31 #include "audio/AudioSource.h"
32 #include "io/log/Logger.h"
33 #include "math/Vector3.h"
34 #include "platform/Platform.h"
35 #include "platform/CrashHandler.h"
36
37 namespace audio {
38
39 class Sample;
40
41 #undef ALError
42 #define ALError LogError
43
OpenALBackend()44 OpenALBackend::OpenALBackend() : device(NULL), context(NULL),
45 #ifdef ARX_HAVE_OPENAL_EFX
46 hasEFX(false), effectEnabled(false),
47 #endif
48 rolloffFactor(1.f) {
49
50 }
51
~OpenALBackend()52 OpenALBackend::~OpenALBackend() {
53
54 sources.clear();
55
56 if(context) {
57
58 alcDestroyContext(context);
59
60 ALenum error = alcGetError(device);
61 if(error != AL_NO_ERROR) {
62 LogError << "Error destroying OpenAL context: " << error << " = " << getAlcErrorString(error);
63 }
64 }
65
66 if(device) {
67 if(alcCloseDevice(device) == ALC_FALSE) {
68 LogError << "Error closing device";
69 }
70 }
71 }
72
init(bool enableEffects)73 aalError OpenALBackend::init(bool enableEffects) {
74
75 if(device) {
76 return AAL_ERROR_INIT;
77 }
78
79 // clear error
80 alGetError();
81
82 // Create OpenAL interface
83 device = alcOpenDevice(NULL);
84 if(!device) {
85 ALenum error = alcGetError(NULL);
86 LogError << "Error opening device: " << error << " = " << getAlcErrorString(error);
87 return AAL_ERROR_SYSTEM;
88 }
89
90 context = alcCreateContext(device, NULL);
91 if(!context) {
92 ALenum error = alcGetError(device);
93 LogError << "Error creating OpenAL context: " << error << " = " << getAlcErrorString(error);
94 return AAL_ERROR_SYSTEM;
95 }
96 alcMakeContextCurrent(context);
97
98 #ifdef ARX_HAVE_OPENAL_EFX
99 hasEFX = enableEffects && alcIsExtensionPresent(device, "ALC_EXT_EFX");
100 if(enableEffects && !hasEFX) {
101 LogWarning << "Cannot enable effects, missing the EFX extension";
102 }
103 if(hasEFX) {
104 alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
105 alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
106 alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
107 arx_assert(alGenEffects && alDeleteEffects && alEffectf);
108 }
109 #else
110 ARX_UNUSED(enableEffects);
111 #endif
112
113 alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
114
115 AL_CHECK_ERROR("initializing")
116
117 const ALchar * renderer = alGetString(AL_RENDERER);
118 const ALchar * version = alGetString(AL_VERSION);
119 const char * efx_ver;
120 #ifdef ARX_HAVE_OPENAL_EFX
121 if(hasEFX) {
122 efx_ver = " with EFX";
123 }
124 else
125 #endif
126 {
127 efx_ver = " without EFX";
128 }
129 const char * prefix = "";
130 if(std::strncmp(renderer, "OpenAL", 6) != 0) {
131 prefix = "OpenAL ";
132 }
133 LogInfo << "Using " << prefix << renderer << ' ' << version << efx_ver;
134 CrashHandler::setVariable("OpenAL renderer", renderer);
135 CrashHandler::setVariable("OpenAL version", version);
136
137 LogInfo << " └─ Vendor: " << alGetString(AL_VENDOR);
138 CrashHandler::setVariable("OpenAL vendor", alGetString(AL_VENDOR));
139
140 LogDebug("AL extensions: " << alGetString(AL_EXTENSIONS));
141 LogDebug("ALC extensions: " << alcGetString(device, ALC_EXTENSIONS));
142
143 return AAL_OK;
144 }
145
updateDeferred()146 aalError OpenALBackend::updateDeferred() {
147
148 // Nothing to do here.
149
150 return AAL_OK;
151 }
152
createSource(SampleId sampleId,const Channel & channel)153 Source * OpenALBackend::createSource(SampleId sampleId, const Channel & channel) {
154
155 SampleId s_id = getSampleId(sampleId);
156
157 if(!_sample.isValid(s_id)) {
158 return NULL;
159 }
160
161 Sample * sample = _sample[s_id];
162
163 OpenALSource * orig = NULL;
164 for(size_t i = 0; i < sources.size(); i++) {
165 if(sources[i] && sources[i]->getSample() == sample) {
166 orig = (OpenALSource*)sources[i];
167 break;
168 }
169 }
170
171 OpenALSource * source = new OpenALSource(sample);
172
173 size_t index = sources.add(source);
174 if(index == (size_t)INVALID_ID) {
175 delete source;
176 return NULL;
177 }
178
179 SourceId id = (index << 16) | s_id;
180 if(source->init(id, orig, channel)) {
181 sources.remove(index);
182 return NULL;
183 }
184
185 source->setRolloffFactor(rolloffFactor);
186
187 return source;
188 }
189
getSource(SourceId sourceId)190 Source * OpenALBackend::getSource(SourceId sourceId) {
191
192 size_t index = ((sourceId >> 16) & 0x0000ffff);
193 if(!sources.isValid(index)) {
194 return NULL;
195 }
196
197 Source * source = sources[index];
198
199 SampleId sample = getSampleId(sourceId);
200 if(!_sample.isValid(sample) || source->getSample() != _sample[sample]) {
201 return NULL;
202 }
203
204 arx_assert(source->getId() == sourceId);
205
206 return source;
207 }
208
setRolloffFactor(float factor)209 aalError OpenALBackend::setRolloffFactor(float factor) {
210
211 rolloffFactor = factor;
212 for(size_t i = 0; i < sources.size(); i++) {
213 if(sources[i]) {
214 sources[i]->setRolloffFactor(rolloffFactor);
215 }
216 }
217
218 return AAL_OK;
219 }
220
setListenerPosition(const Vec3f & position)221 aalError OpenALBackend::setListenerPosition(const Vec3f & position) {
222
223 if(!isallfinite(position)) {
224 return AAL_ERROR; // OpenAL soft will lock up if given NaN or +-Inf here
225 }
226
227 alListener3f(AL_POSITION, position.x, position.y, position.z);
228 AL_CHECK_ERROR("setting listener posiotion")
229
230 return AAL_OK;
231 }
232
setListenerOrientation(const Vec3f & front,const Vec3f & up)233 aalError OpenALBackend::setListenerOrientation(const Vec3f & front, const Vec3f & up) {
234
235 if(!isallfinite(front) || !isallfinite(up)) {
236 return AAL_ERROR; // OpenAL soft will lock up if given NaN or +-Inf here
237 }
238
239 ALfloat orientation[] = {front.x, front.y, front.z, -up.x, -up.y, -up.z};
240 alListenerfv(AL_ORIENTATION, orientation);
241 AL_CHECK_ERROR("setting listener orientation")
242
243 return AAL_OK;
244 }
245
sourcesBegin()246 Backend::source_iterator OpenALBackend::sourcesBegin() {
247 return (source_iterator)sources.begin();
248 }
249
sourcesEnd()250 Backend::source_iterator OpenALBackend::sourcesEnd() {
251 return (source_iterator)sources.end();
252 }
253
deleteSource(source_iterator it)254 Backend::source_iterator OpenALBackend::deleteSource(source_iterator it) {
255 arx_assert(it >= sourcesBegin() && it < sourcesEnd());
256 return (source_iterator)sources.remove((ResourceList<OpenALSource>::iterator)it);
257 }
258
setUnitFactor(float factor)259 aalError OpenALBackend::setUnitFactor(float factor) {
260
261 #ifdef ARX_HAVE_OPENAL_EFX
262 if(hasEFX) {
263 alListenerf(AL_METERS_PER_UNIT, factor);
264 AL_CHECK_ERROR("setting unit factor")
265 }
266 #endif
267
268 const float speedOfSoundMetersPerSecond = 343.3f; // Default for OpenAL
269
270 float speedOfSoundInUnits = speedOfSoundMetersPerSecond / factor;
271
272 if(!(boost::math::isfinite)(speedOfSoundInUnits)) {
273 return AAL_ERROR; // OpenAL soft will lock up if given NaN or +-Inf here
274 }
275
276 alSpeedOfSound(speedOfSoundInUnits);
277 AL_CHECK_ERROR("scaling speed of sound to unit factor")
278
279 return AAL_OK;
280 }
281
282 #ifdef ARX_HAVE_OPENAL_EFX
283
setReverbEnabled(bool enable)284 aalError OpenALBackend::setReverbEnabled(bool enable) {
285
286 ARX_UNUSED(enable);
287
288 // TODO implement reverb
289
290 return AAL_OK;
291 }
292
setRoomRolloffFactor(float factor)293 aalError OpenALBackend::setRoomRolloffFactor(float factor) {
294
295 if(!effectEnabled) {
296 return AAL_ERROR_INIT;
297 }
298
299 float rolloff = clamp(factor, 0.f, 10.f);
300
301 return setEffect(AL_REVERB_ROOM_ROLLOFF_FACTOR, rolloff);
302 }
303
setListenerEnvironment(const Environment & env)304 aalError OpenALBackend::setListenerEnvironment(const Environment & env) {
305
306 if(!effectEnabled) {
307 return AAL_ERROR_INIT;
308 }
309
310 // TODO implement reverb - not all properties are set, some may be wrong
311
312 setEffect(AL_REVERB_DIFFUSION, env.diffusion);
313 setEffect(AL_REVERB_AIR_ABSORPTION_GAINHF, env.absorption * -100.f);
314 setEffect(AL_REVERB_LATE_REVERB_GAIN, clamp(env.reverb_volume, 0.f, 1.f));
315 setEffect(AL_REVERB_LATE_REVERB_DELAY, clamp(env.reverb_delay, 0.f, 100.f) * 0.001f);
316 setEffect(AL_REVERB_DECAY_TIME, clamp(env.reverb_decay, 100.f, 20000.f) * 0.001f);
317 setEffect(AL_REVERB_DECAY_HFRATIO, clamp(env.reverb_hf_decay / env.reverb_decay, 0.1f, 2.f));
318 setEffect(AL_REVERB_REFLECTIONS_GAIN, clamp(env.reflect_volume, 0.f, 1.f));
319 setEffect(AL_REVERB_REFLECTIONS_DELAY, clamp(env.reflect_delay, 0.f, 300.f) * 0.001F);
320
321 return AAL_OK;
322 }
323
setEffect(ALenum type,float val)324 aalError OpenALBackend::setEffect(ALenum type, float val) {
325
326 alEffectf(effect, type, val);
327 AL_CHECK_ERROR("setting effect var");
328
329 return AAL_OK;
330 }
331
332 #else // ARX_HAVE_OPENAL_EFX
333
setReverbEnabled(bool enable)334 aalError OpenALBackend::setReverbEnabled(bool enable) {
335 ARX_UNUSED(enable);
336 return AAL_ERROR_SYSTEM;
337 }
338
setRoomRolloffFactor(float factor)339 aalError OpenALBackend::setRoomRolloffFactor(float factor) {
340 ARX_UNUSED(factor);
341 return AAL_ERROR_SYSTEM;
342 }
343
setListenerEnvironment(const Environment & env)344 aalError OpenALBackend::setListenerEnvironment(const Environment & env) {
345 ARX_UNUSED(env);
346 return AAL_ERROR_SYSTEM;
347 }
348
349 #endif // ARX_HAVE_OPENAL_EFX
350
351 } // namespace audio
352