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