]> Dogcows Code - chaz/yoink/blob - src/Moof/Sound.cc
bugfix: win32 packaging script temp directories
[chaz/yoink] / src / Moof / Sound.cc
1
2 /*] Copyright (c) 2009-2010, Charles McGarvey [**************************
3 **] All rights reserved.
4 *
5 * vi:ts=4 sw=4 tw=75
6 *
7 * Distributable under the terms and conditions of the 2-clause BSD license;
8 * see the file COPYING for a complete text of the license.
9 *
10 **************************************************************************/
11
12 #include <cstdio>
13 #include <deque>
14 #include <list>
15 #include <string>
16
17 #include <boost/algorithm/string.hpp>
18
19 #include <AL/al.h>
20 #include <AL/alc.h>
21 #include <vorbis/codec.h>
22 #include <vorbis/vorbisfile.h>
23
24 #include "Error.hh"
25 #include "Manager.hh"
26 #include "Log.hh"
27 #include "Sound.hh"
28 #include "Timer.hh"
29
30 #define BUFFER_SIZE (64 * 1024)
31 //#define BUFFER_SIZE (5*2048)
32
33 namespace Mf {
34
35
36 class Sound::Impl
37 {
38 public:
39
40 static ALenum getAudioFormat(const vorbis_info* audioInfo)
41 {
42 if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
43 else return AL_FORMAT_STEREO16;
44 }
45
46
47 class Buffer;
48 typedef boost::shared_ptr<Buffer> BufferP;
49
50 class Buffer : public Manager<Buffer>
51 {
52 public:
53
54 Buffer() :
55 mBuffer(-1)
56 {
57 mOggStream.datasource = 0;
58 }
59
60 ~Buffer()
61 {
62 if (mOggStream.datasource)
63 {
64 ov_clear(&mOggStream);
65 }
66 if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
67 }
68
69
70 void init(const std::string& name)
71 {
72 if (mOggStream.datasource)
73 {
74 ov_clear(&mOggStream);
75 mOggStream.datasource = 0;
76 }
77
78 std::string path(name);
79 if (!Sound::getPath(path))
80 {
81 Error(Error::RESOURCE_NOT_FOUND, path).raise();
82 }
83
84 if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
85 {
86 Error(Error::UNKNOWN_AUDIO_FORMAT, path).raise();
87 }
88
89 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
90 mFormat = getAudioFormat(vorbisInfo);
91 mFreq = vorbisInfo->rate;
92 }
93
94
95 void loadAll(ALuint source)
96 {
97 if (!mOggStream.datasource) init(getName());
98 if (!mOggStream.datasource) return;
99
100 char data[BUFFER_SIZE];
101 int size = 0;
102
103 for (;;)
104 {
105 int section;
106 int result = ov_read(&mOggStream, data + size,
107 BUFFER_SIZE - size, 0, 2, 1, &section);
108
109 if (result > 0)
110 {
111 size += result;
112 }
113 else
114 {
115 if (result < 0) logWarning("vorbis playback error");
116 break;
117 }
118 }
119 if (size == 0)
120 {
121 logWarning << "decoded no bytes from "
122 << getName() << std::endl;
123 return;
124 }
125
126 alGenBuffers(1, &mBuffer);
127
128 alBufferData(mBuffer, mFormat, data, size, mFreq);
129 alSourcei(source, AL_BUFFER, mBuffer);
130
131 // don't need to keep this loaded
132 ov_clear(&mOggStream);
133 mOggStream.datasource = 0;
134 }
135
136 bool stream(ALuint buffer)
137 {
138 char data[BUFFER_SIZE];
139 int size = 0;
140
141 while (size < BUFFER_SIZE)
142 {
143 int section;
144 int result = ov_read(&mOggStream, data + size,
145 BUFFER_SIZE - size, 0, 2, 1, &section);
146
147 if (result > 0)
148 {
149 size += result;
150 }
151 else
152 {
153 if (result < 0) logWarning("vorbis playback error");
154 break;
155 }
156 }
157
158 if (size == 0) return false;
159
160 alBufferData(buffer, mFormat, data, size, mFreq);
161
162 return true;
163 }
164
165 void rewind()
166 {
167 if (!mOggStream.datasource) init(getName());
168 else ov_raw_seek(&mOggStream, 0);
169 }
170
171
172 private:
173
174 OggVorbis_File mOggStream;
175 ALenum mFormat;
176 ALsizei mFreq;
177 ALuint mBuffer;
178 };
179
180
181 Impl()
182 {
183 init();
184 }
185
186 Impl(const std::string& name)
187 {
188 init();
189 enqueue(name);
190 }
191
192 void init()
193 {
194 retainBackend();
195
196 mIsLoaded = false;
197 mIsPlaying = false;
198 mIsLooping = false;
199
200 alGenSources(1, &mSource);
201
202 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
203 alSourcef(mSource, AL_PITCH, 1.0f);
204 alSourcef(mSource, AL_GAIN, 1.0f);
205 alSourcefv(mSource, AL_POSITION, zero);
206 alSourcefv(mSource, AL_VELOCITY, zero);
207
208 alSourcei(mSource, AL_LOOPING, mIsLooping);
209 }
210
211 ~Impl()
212 {
213 stop();
214
215 alDeleteSources(1, &mSource);
216
217 while (!mBuffers.empty())
218 {
219 alDeleteBuffers(1, &mBuffers.back());
220 mBuffers.pop_back();
221 }
222
223 releaseBackend();
224 }
225
226
227 void play()
228 {
229 if (mQueue.empty()) return;
230
231 if (!mIsLoaded) mQueue.front()->loadAll(mSource);
232
233 alSourcePlay(mSource);
234 mIsLoaded = true;
235 }
236
237
238 void playStream()
239 {
240 if (mQueue.empty()) return;
241
242 if (!mIsPlaying)
243 {
244 alSourcei(mSource, AL_LOOPING, false);
245 bufferStream();
246 }
247
248 if (!mStreamTimer.isValid())
249 {
250 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
251 1.0, Timer::REPEAT);
252 }
253
254 alSourcePlay(mSource);
255 mIsPlaying = true;
256 }
257
258 void bufferStream()
259 {
260 ALuint buffer;
261 for (int i = mBuffers.size(); i <= 8; ++i)
262 {
263 alGenBuffers(1, &buffer);
264
265 if (mQueue.front()->stream(buffer))
266 {
267 alSourceQueueBuffers(mSource, 1, &buffer);
268 mBuffers.push_back(buffer);
269 }
270 else
271 {
272 alDeleteBuffers(1, &buffer);
273 break;
274 }
275 }
276 }
277
278
279 void update()
280 {
281 ALint finished = 0;
282
283 alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished);
284
285 while (finished-- > 0)
286 {
287 ALuint bufferObj;
288 alSourceUnqueueBuffers(mSource, 1, &bufferObj);
289
290 BufferP buffer = mQueue.front();
291 bool streamed = buffer->stream(bufferObj);
292
293 if (streamed)
294 {
295 alSourceQueueBuffers(mSource, 1, &bufferObj);
296 }
297 else
298 {
299 // the buffer couldn't be streamed, so get rid of it
300 mQueue.pop_front();
301
302 if (!mQueue.empty())
303 {
304 // begin the next buffer in the queue
305 mQueue.front()->rewind();
306 mQueue.front()->stream(bufferObj);
307 alSourceQueueBuffers(mSource, 1, &bufferObj);
308 logInfo("loading new buffer");
309
310 // queue up any unused buffers
311 bufferStream();
312 }
313 else if (mIsLooping)
314 {
315 // reload the same buffer
316 mQueue.push_back(buffer);
317 buffer->rewind();
318 buffer->stream(bufferObj);
319 alSourceQueueBuffers(mSource, 1, &bufferObj);
320 logInfo("looping same buffer");
321 }
322 else
323 {
324 // nothing more to play, stopping...
325 mIsPlaying = false;
326 std::remove(mBuffers.begin(), mBuffers.end(),
327 bufferObj);
328 }
329 }
330 }
331
332 ALenum state;
333 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
334
335 // restart playing if we're stopped but supposed to be playing...
336 // this means we didn't queue enough and the audio skipped :-(
337 if (mIsPlaying && state != AL_PLAYING)
338 {
339 alSourcePlay(mSource);
340 }
341 }
342
343
344 void stop()
345 {
346 alSourceStop(mSource);
347 mIsPlaying = false;
348
349 mStreamTimer.invalidate();
350 }
351
352 void pause()
353 {
354 alSourcePause(mSource);
355 mIsPlaying = false;
356
357 mStreamTimer.invalidate();
358 }
359
360
361 void setSample(const std::string& name)
362 {
363 stop();
364 alSourcei(mSource, AL_BUFFER, AL_NONE);
365
366 mQueue.clear();
367 mIsLoaded = false;
368
369 enqueue(name);
370
371 while (!mBuffers.empty())
372 {
373 alDeleteBuffers(1, &mBuffers.back());
374 mBuffers.pop_back();
375 }
376 }
377
378 void enqueue(const std::string& name)
379 {
380 BufferP buffer = Buffer::getInstance(name);
381 mQueue.push_back(buffer);
382 }
383
384
385 bool isPlaying() const
386 {
387 if (mIsPlaying) return true;
388
389 ALenum state;
390 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
391
392 return state == AL_PLAYING;
393 }
394
395
396 void setLooping(bool looping)
397 {
398 mIsLooping = looping;
399
400 ALenum type;
401 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
402
403 if (type != AL_STREAMING)
404 {
405 alSourcei(mSource, AL_LOOPING, mIsLooping);
406 }
407 }
408
409
410 void streamUpdate(Timer& timer, Scalar t)
411 {
412 // don't let the music die!
413 update();
414 // TODO - might be nice to also allow using threads for streaming
415 // rather than a timer, probably as a compile-time option
416 }
417
418 static void retainBackend()
419 {
420 if (gRetainCount++ == 0)
421 {
422 gAlDevice = alcOpenDevice(0);
423 gAlContext = alcCreateContext(gAlDevice, 0);
424 if (!gAlDevice || !gAlContext)
425 {
426 const char* error = alcGetString(gAlDevice,
427 alcGetError(gAlDevice));
428 logError << "audio subsystem initialization failure: "
429 << error << std::endl;
430 }
431 else
432 {
433 alcMakeContextCurrent(gAlContext);
434 logInfo << "opened sound device `"
435 << alcGetString(gAlDevice,
436 ALC_DEFAULT_DEVICE_SPECIFIER)
437 << "'" << std::endl;
438 }
439 }
440 }
441
442 static void releaseBackend()
443 {
444 if (--gRetainCount == 0)
445 {
446 alcMakeContextCurrent(0);
447 alcDestroyContext(gAlContext);
448 alcCloseDevice(gAlDevice);
449 }
450 }
451
452
453 ALuint mSource;
454 std::list<ALuint> mBuffers;
455
456 bool mIsLoaded;
457 bool mIsPlaying;
458 bool mIsLooping;
459
460 std::deque<BufferP> mQueue;
461
462 Timer mStreamTimer;
463
464 static unsigned gRetainCount;
465 static ALCdevice* gAlDevice;
466 static ALCcontext* gAlContext;
467 };
468
469 unsigned Sound::Impl::gRetainCount = 0;
470 ALCdevice* Sound::Impl::gAlDevice = 0;
471 ALCcontext* Sound::Impl::gAlContext = 0;
472
473
474 Sound::Sound() :
475 // pass through
476 mImpl(new Sound::Impl) {}
477
478 Sound::Sound(const std::string& name) :
479 // pass through
480 mImpl(new Sound::Impl(name)) {}
481
482
483 void Sound::setSample(const std::string& name)
484 {
485 // pass through
486 mImpl->setSample(name);
487 }
488
489
490 void Sound::play()
491 {
492 // pass through
493 mImpl->play();
494 }
495
496 void Sound::stop()
497 {
498 // pass through
499 mImpl->stop();
500 }
501
502 void Sound::pause()
503 {
504 // pass through
505 mImpl->pause();
506 }
507
508
509 void Sound::toggle()
510 {
511 if (isPlaying()) pause();
512 else play();
513 }
514
515 bool Sound::isPlaying() const
516 {
517 // pass through
518 return mImpl->isPlaying();
519 }
520
521
522 void Sound::setPosition(const Vector3& position)
523 {
524 float vec[3] = {position[0], position[1], position[2]};
525 alSourcefv(mImpl->mSource, AL_POSITION, vec);
526 }
527
528 void Sound::setVelocity(const Vector3& velocity)
529 {
530 float vec[3] = {velocity[0], velocity[1], velocity[2]};
531 alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
532 }
533
534 void Sound::setGain(Scalar gain)
535 {
536 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
537 }
538
539 void Sound::setPitch(Scalar pitch)
540 {
541 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
542 }
543
544 void Sound::setLooping(bool looping)
545 {
546 // pass through
547 mImpl->setLooping(looping);
548 }
549
550
551 void Sound::setListenerPosition(const Vector3& position)
552 {
553 float vec[] = {position[0], position[1], position[2]};
554 alListenerfv(AL_POSITION, vec);
555 }
556
557 void Sound::setListenerVelocity(const Vector3& velocity)
558 {
559 float vec[] = {velocity[0], velocity[1], velocity[2]};
560 alListenerfv(AL_VELOCITY, vec);
561 }
562
563 void Sound::setListenerOrientation(const Vector3& forward,
564 const Vector3& up)
565 {
566 float vec[6];
567 vec[0] = float(forward[0]);
568 vec[1] = float(forward[1]);
569 vec[2] = float(forward[2]);
570 vec[3] = float(up[0]);
571 vec[4] = float(up[1]);
572 vec[5] = float(up[2]);
573 alListenerfv(AL_ORIENTATION, vec);
574 }
575
576
577 bool Sound::getPath(std::string& name)
578 {
579 return Resource::getPath(name, "sounds/", "ogg");
580 }
581
582
583 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
584
585
586 void SoundStream::enqueue(const std::string& name)
587 {
588 // pass through
589 mImpl->enqueue(name);
590 }
591
592
593 void SoundStream::play()
594 {
595 // pass through
596 mImpl->playStream();
597 }
598
599
600 } // namespace Mf
601
This page took 0.055637 seconds and 4 git commands to generate.