5 * Created by Bryan Donlan on Sun Aug 12 2007.
6 * Copyright (c) 2007 Bryan Donlan. All rights reserved.
8 * This library is free software; you can redistribute it and/or
9 * modify it under the terms of the GNU Lesser General Public
10 * License as published by the Free Software Foundation; either
11 * version 2 of the License, or (at your option) any later version.
13 * This library is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 * Lesser General Public License for more details.
20 #include "OpenALBackend.h"
23 #include <boost/format.hpp>
24 #include <boost/thread.hpp>
25 #include <boost/lambda/bind.hpp>
27 #include "exceptions.h"
30 #ifndef OPENAL_SUPPORT
31 #error OPENAL_SUPPORT isn't set, so this file shouldn't be being compiled
34 const ALfloat zdist
= -1.0;
35 const ALfloat plnemul
= 0.0;
36 const ALfloat scale
= 1.0/100;
38 static const ALfloat null_vec
[] = { 0.0, 0.0, 0.0 };
40 static std::string
al_error_str(ALenum err
) {
43 return std::string("No error");
45 return std::string("Invalid name");
47 return std::string("Invalid enum");
48 case AL_INVALID_VALUE
:
49 return std::string("Invalid value");
50 case AL_INVALID_OPERATION
:
51 return std::string("Invalid operation");
52 case AL_OUT_OF_MEMORY
:
53 return std::string("Out of memory (we're crashing soon)");
55 return boost::str(boost::format("Unknown error %d") % (int)err
);
59 static void al_throw_maybe() {
60 ALenum err
= alGetError();
61 if (err
== AL_NO_ERROR
) return;
62 throw creaturesException(boost::str(boost::format("OpenAL error (%d): %s") % err
% al_error_str(err
)));
65 void OpenALBackend::init() {
69 if (!alutInitWithoutContext(NULL
, NULL
)) {
70 ALenum err
= alutGetError();
71 throw creaturesException(boost::str(
72 boost::format("Failed to init OpenAL/ALUT: %s") % alutGetErrorString(err
)
76 // It seems that relying on hardware drivers is an awful idea - always use software if it's available.
77 // TODO: we should probably store this so we can (eventually) close stuff properly
78 ALCdevice
*device
= alcOpenDevice("Generic Software");
80 device
= alcOpenDevice(NULL
);
85 ALCcontext
*context
= alcCreateContext(device
, NULL
);
89 alcMakeContextCurrent(context
);
93 static const ALfloat init_ori
[6] = {
94 0.0, 0.0, 1.0, /* at */
95 0.0, -1.0, 0.0 /* up */
97 static const ALfloat init_vel
[3] = {
100 static const ALfloat init_pos
[3] = {
103 memcpy((void *)ListenerOri
, (void *)init_ori
, sizeof init_ori
);
104 memcpy((void *)ListenerVel
, (void *)init_vel
, sizeof init_vel
);
105 memcpy((void *)ListenerPos
, (void *)init_pos
, sizeof init_pos
);
107 ListenerPos
[2] = zdist
;
111 void OpenALBackend::updateListener() {
112 alListenerfv(AL_POSITION
, ListenerPos
);
113 alListenerfv(AL_VELOCITY
, ListenerVel
);
114 alListenerfv(AL_ORIENTATION
, ListenerOri
);
117 void OpenALBackend::setViewpointCenter(float x
, float y
) {
118 ListenerPos
[0] = x
* scale
;
119 ListenerPos
[1] = y
* scale
;
123 void OpenALBackend::shutdown() {
127 void OpenALBackend::setMute(bool m
) {
129 alListenerf(AL_GAIN
, 0.0f
);
131 alListenerf(AL_GAIN
, 1.0f
);
135 boost::shared_ptr
<AudioSource
> OpenALBackend::newSource() {
136 return boost::shared_ptr
<AudioSource
>(new OpenALSource(shared_from_this()));
139 AudioClip
OpenALBackend::loadClip(const std::string
&filename
) {
140 std::string fname
= world
.findFile(std::string("/Sounds/") + filename
+ ".wav");
141 if (fname
.size() == 0) return AudioClip();
144 ALuint buf
= alutCreateBufferFromFile(fname
.c_str());
146 ALenum err
= alGetError();
147 throw creaturesException(boost::str(
148 boost::format("Failed to load %s: %s") % fname
% alutGetErrorString(err
)
152 AudioClip
clip(new OpenALBuffer(shared_from_this(), buf
));
156 void OpenALBackend::begin() {
157 alcSuspendContext(alcGetCurrentContext());
160 void OpenALBackend::commit() {
161 alcProcessContext(alcGetCurrentContext());
164 OpenALSource::OpenALSource(boost::shared_ptr
<class OpenALBackend
> backend
) {
165 this->backend
= backend
;
167 alGenSources(1, &source
);
168 alSourcef(source
, AL_PITCH
, 1.0f
);
169 alSourcef(source
, AL_GAIN
, 1.0f
);
170 alSourcefv(source
, AL_POSITION
, null_vec
);
171 alSourcefv(source
, AL_VELOCITY
, null_vec
);
172 alSourcei(source
, AL_LOOPING
, 0);
176 OpenALBuffer::OpenALBuffer(boost::shared_ptr
<class OpenALBackend
> backend
, ALuint handle
) {
177 this->backend
= backend
;
181 unsigned int OpenALBuffer::length_samples() {
185 alGetBufferi(buffer
, AL_BITS
, &bits
);
186 alGetBufferi(buffer
, AL_CHANNELS
, &channels
);
187 alGetBufferi(buffer
, AL_SIZE
, &size
);
188 return size
/ (bits
/ 8 * channels
);
191 unsigned int OpenALBuffer::length_ms() {
193 alGetBufferi(buffer
, AL_FREQUENCY
, &freq
);
194 return length_samples() * 1000 / freq
;
197 AudioClip
OpenALSource::getClip() {
198 AudioClip
clip(static_cast<AudioBuffer
*>(this->clip
.get()));
202 void OpenALSource::setClip(AudioClip
&clip_
) {
203 OpenALBuffer
*obp
= dynamic_cast<OpenALBuffer
*>(clip_
.get());
206 this->clip
= OpenALClip(obp
);
208 alSourcei(source
, AL_BUFFER
, clip
->buffer
);
212 SourceState
OpenALSource::getState() {
214 alGetSourcei(source
, AL_SOURCE_STATE
, &state
);
216 case AL_INITIAL
: case AL_STOPPED
: return SS_STOP
;
217 case AL_PLAYING
: return SS_PLAY
; /* XXX: AL_INITIAL? */
218 case AL_PAUSED
: return SS_PAUSE
;
221 std::cerr
<< "Unknown openal state, stopping stream: " << state
<< std::endl
;
228 void OpenALSource::play() {
230 alSourcePlay(source
);
233 void OpenALSource::stop() {
234 alSourceStop(source
);
237 void OpenALSource::pause() {
238 alSourcePause(source
);
241 static void fadeSource(boost::weak_ptr
<AudioSource
> s
) {
242 int fadeout
= 500; // TODO: good value?
244 /* We adjust gain every 10ms until we reach fadeout ms. */
245 const int steps
= fadeout
/ 10;
246 const float adjust
= 1.0 / (float)steps
;
247 for (int i
= 0; i
< steps
; i
++) {
248 boost::shared_ptr
<AudioSource
> p
= s
.lock();
250 p
->setVolume(1.0f
- adjust
* (float)i
);
251 SDL_Delay(10); // XXX: do we have some other portable sleeping primitive we can use?
252 // boost::thread::sleep is a bit iffy, it seems
254 boost::shared_ptr
<AudioSource
> p
= s
.lock();
259 void OpenALSource::fadeOut() {
260 boost::thread
th(boost::lambda::bind
<void>(fadeSource
, shared_from_this()));
263 void OpenALSource::setPos(float x
, float y
, float plane
) {
264 if (this->x
== x
&& this->y
== y
&& this->z
== plane
) return;
265 this->SkeletonAudioSource::setPos(x
, y
, plane
);
266 alSource3f(source
, AL_POSITION
, x
* scale
, y
* scale
, plane
* plnemul
);
269 void OpenALSource::setVelocity(float x
, float y
) {
270 // experiment with this later
272 alSource3f(source
, AL_VELOCITY
, x
* scale
, y
* scale
, 0);
275 bool OpenALSource::isLooping() {
277 alGetSourcei(source
, AL_LOOPING
, &l
);
281 void OpenALSource::setLooping(bool l
) {
282 alSourcei(source
, AL_LOOPING
, l
? AL_TRUE
: AL_FALSE
);
285 void OpenALSource::setVolume(float v
) {
286 if (v
== this->volume
) return;
287 this->SkeletonAudioSource::setVolume(v
);
288 alSourcef(source
, AL_GAIN
, getEffectiveVolume());
291 void OpenALSource::setMute(bool m
) {
292 if (m
== muted
) return;
293 this->SkeletonAudioSource::setMute(m
);
294 alSourcef(source
, AL_GAIN
, getEffectiveVolume());