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