4275ea4485900c67e147aeb04165b5f6c2496b30
[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 <string>
32 #include <vector>
33
34 #include <AL/al.h>
35 #include <vorbis/codec.h>
36 #include <vorbis/vorbisfile.h>
37
38 #include "Engine.hh"
39 #include "Exception.hh"
40 #include "Library.hh"
41 #include "Log.hh"
42 #include "Sound.hh"
43 #include "Timer.hh"
44
45 #define BUFFER_SIZE (64 * 1024)
46 //#define BUFFER_SIZE (5*2048)
47
48 namespace Mf {
49
50
51 class Sound::Impl
52 {
53 public:
54
55 static ALenum getAudioFormat(const vorbis_info* audioInfo)
56 {
57 if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
58 else return AL_FORMAT_STEREO16;
59 }
60
61
62 class Buffer;
63 typedef boost::shared_ptr<Buffer> BufferP;
64
65 class Buffer : public Library<Buffer>
66 {
67 public:
68
69 Buffer(const std::string& name) :
70 Library<Buffer>(name),
71 mBuffer(-1)
72 {
73 mOggStream.datasource = 0;
74 openFile();
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 openFile()
88 {
89 if (mOggStream.datasource)
90 {
91 ov_clear(&mOggStream);
92 mOggStream.datasource = 0;
93 }
94
95 std::string filePath = Sound::getPath(getName());
96 int result = ov_fopen((char*)filePath.c_str(), &mOggStream);
97
98 if (result < 0)
99 {
100 logWarning("error while loading sound %s",
101 getName().c_str());
102 throw Exception(ErrorCode::UNKNOWN_AUDIO_FORMAT, getName());
103 }
104
105 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
106 mFormat = getAudioFormat(vorbisInfo);
107 mFreq = vorbisInfo->rate;
108 }
109
110
111 void loadAll(ALuint source)
112 {
113 if (!mOggStream.datasource) openFile();
114 if (!mOggStream.datasource) return;
115
116 char data[BUFFER_SIZE];
117 int size = 0;
118
119 for (;;)
120 {
121 int section;
122 int result = ov_read(&mOggStream, data + size,
123 BUFFER_SIZE - size, 0, 2, 1, &section);
124
125 if (result > 0)
126 {
127 size += result;
128 }
129 else
130 {
131 if (result < 0) logWarning("vorbis playback error");
132 break;
133 }
134 }
135 if (size == 0)
136 {
137 logWarning("decoded no bytes from %s", getName().c_str());
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) openFile();
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 // make sure the engine is initialized
210 Engine::getInstance();
211
212 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
213
214 alGenSources(1, &mSource);
215
216 alSourcef(mSource, AL_PITCH, 1.0f);
217 alSourcef(mSource, AL_GAIN, 1.0f);
218 alSourcefv(mSource, AL_POSITION, zero);
219 alSourcefv(mSource, AL_VELOCITY, zero);
220
221 mIsPlaying = false;
222 mIsLooping = false;
223 }
224
225 ~Impl()
226 {
227 stop();
228
229 alDeleteSources(1, &mSource);
230
231 while (!mBufferObjects.empty())
232 {
233 alDeleteBuffers(1, &mBufferObjects.back());
234 mBufferObjects.pop_back();
235 }
236 }
237
238
239 void play()
240 {
241 if (mQueue.empty()) return;
242
243 ALenum type;
244 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
245
246 if (type != AL_STATIC)
247 {
248 mQueue.front()->loadAll(mSource);
249 }
250
251 alSourcei(mSource, AL_LOOPING, mIsLooping);
252 alSourcePlay(mSource);
253 mIsPlaying = true;
254 }
255
256
257 void stream()
258 {
259 stop();
260
261 alSourcei(mSource, AL_BUFFER, AL_NONE);
262 mQueue.front()->rewind();
263 beginStream();
264
265 alSourcei(mSource, AL_LOOPING, AL_FALSE);
266 alSourcePlay(mSource);
267 mIsPlaying = true;
268
269 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2), 1.0,
270 Timer::REPEAT);
271 }
272
273 void beginStream()
274 {
275 ALuint buffer;
276 for (int i = mBufferObjects.size(); i < 8; ++i)
277 {
278 alGenBuffers(1, &buffer);
279 mBufferObjects.push_back(buffer);
280 }
281 for (int i = 0; i < 8; ++i)
282 {
283 buffer = mBufferObjects[i];
284 mQueue.front()->stream(buffer);
285 alSourceQueueBuffers(mSource, 1, &buffer);
286 }
287 }
288
289
290 void update()
291 {
292 ALint finished = 0;
293
294 alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished);
295
296 while (finished-- > 0)
297 {
298 ALuint bufferObj;
299 alSourceUnqueueBuffers(mSource, 1, &bufferObj);
300
301 BufferP buffer = mQueue.front();
302 bool streamed = buffer->stream(bufferObj);
303
304 if (streamed)
305 {
306 alSourceQueueBuffers(mSource, 1, &bufferObj);
307 }
308 else
309 {
310 // the buffer couldn't be streamed, so get rid of it
311 mQueue.pop_front();
312
313 if (!mQueue.empty())
314 {
315 // begin the next buffer in the queue
316 mQueue.front()->rewind();
317 mQueue.front()->stream(bufferObj);
318 alSourceQueueBuffers(mSource, 1, &bufferObj);
319 logInfo("loading new buffer");
320 }
321 else if (mIsLooping)
322 {
323 // reload the same buffer
324 mQueue.push_back(buffer);
325 buffer->rewind();
326 buffer->stream(bufferObj);
327 alSourceQueueBuffers(mSource, 1, &bufferObj);
328 logInfo("looping same buffer");
329 }
330 }
331 }
332
333 ALenum state;
334 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
335
336 // restart playing if we're stopped but supposed to be playing... this
337 // means we didn't queue enough and the audio skipped :-(
338 if (mIsPlaying && state != AL_PLAYING)
339 {
340 alSourcePlay(mSource);
341 }
342 }
343
344
345 void stop()
346 {
347 alSourceStop(mSource);
348 mIsPlaying = false;
349
350 mStreamTimer.invalidate();
351 }
352
353 void pause()
354 {
355 alSourcePause(mSource);
356 mIsPlaying = false;
357
358 mStreamTimer.invalidate();
359 }
360
361 void resume()
362 {
363 alSourcePlay(mSource);
364 mIsPlaying = true;
365
366 ALenum type;
367 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
368
369 if (type == AL_STREAMING)
370 {
371 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
372 1.0, Timer::REPEAT);
373 }
374 }
375
376
377 void setSample(const std::string& name)
378 {
379 bool playing = isPlaying();
380 ALenum type;
381 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
382
383 stop();
384 mQueue.clear();
385
386 //alSourcei(mSource, AL_BUFFER, AL_NONE);
387 enqueue(name);
388
389 if (playing)
390 {
391 if (type == AL_STREAMING) stream();
392 else play();
393 }
394 }
395
396 void enqueue(const std::string& name)
397 {
398 BufferP buffer = Buffer::getInstance(name);
399 mQueue.push_back(buffer);
400 }
401
402
403 bool isPlaying() const
404 {
405 if (mIsPlaying) return true;
406
407 ALenum state;
408 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
409
410 return state == AL_PLAYING;
411 }
412
413
414 void setLooping(bool looping)
415 {
416 mIsLooping = looping;
417
418 ALenum type;
419 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
420
421 if (type != AL_STREAMING)
422 {
423 alSourcei(mSource, AL_LOOPING, mIsLooping);
424 }
425 }
426
427
428 void streamUpdate(Timer& timer, Scalar t)
429 {
430 // don't let the music die!
431 update();
432 // TODO - might be nice to also allow using threads for streaming rather
433 // than a timer, probably as a compile-time option
434 }
435
436
437 ALuint mSource;
438 std::vector<ALuint> mBufferObjects;
439
440 bool mIsPlaying;
441 bool mIsLooping;
442
443 std::deque<BufferP> mQueue;
444
445 Timer mStreamTimer;
446 };
447
448
449 Sound::Sound() :
450 // pass through
451 mImpl(new Sound::Impl) {}
452
453 Sound::Sound(const std::string& name) :
454 // pass through
455 mImpl(new Sound::Impl(name)) {}
456
457
458 void Sound::play()
459 {
460 // pass through
461 mImpl->play();
462 }
463
464 void Sound::stream()
465 {
466 // pass through
467 mImpl->stream();
468 }
469
470
471 void Sound::stop()
472 {
473 // pass through
474 mImpl->stop();
475 }
476
477 void Sound::pause()
478 {
479 // pass through
480 mImpl->pause();
481 }
482
483 void Sound::resume()
484 {
485 // pass through
486 mImpl->resume();
487 }
488
489 void Sound::toggle()
490 {
491 if (mImpl->mIsPlaying) pause();
492 else resume();
493 }
494
495
496 void Sound::setSample(const std::string& name)
497 {
498 // pass through
499 mImpl->setSample(name);
500 }
501
502 void Sound::enqueue(const std::string& name)
503 {
504 // pass through
505 mImpl->enqueue(name);
506 }
507
508
509 bool Sound::isPlaying() const
510 {
511 // pass through
512 return mImpl->isPlaying();
513 }
514
515 void Sound::setPosition(const Vector3& position)
516 {
517 float p[3] = {position[0], position[1], position[2]};
518 alSourcefv(mImpl->mSource, AL_POSITION, p);
519 }
520
521 void Sound::setVelocity(const Vector3& velocity)
522 {
523 float v[3] = {velocity[0], velocity[1], velocity[2]};
524 alSourcefv(mImpl->mSource, AL_VELOCITY, v);
525 }
526
527 void Sound::setGain(Scalar gain)
528 {
529 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
530 }
531
532 void Sound::setPitch(Scalar pitch)
533 {
534 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
535 }
536
537 void Sound::setLooping(bool looping)
538 {
539 // pass through
540 mImpl->setLooping(looping);
541 }
542
543
544 void Sound::setListenerPosition(const Vector3& position)
545 {
546 alListener3f(AL_POSITION, float(position[0]), float(position[1]),
547 float(position[2]));
548 }
549
550 void Sound::setListenerVelocity(const Vector3& velocity)
551 {
552 alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
553 float(velocity[2]));
554 }
555
556 void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
557 {
558 float vec[6];
559 vec[0] = float(forward[0]);
560 vec[1] = float(forward[1]);
561 vec[2] = float(forward[2]);
562 vec[3] = float(up[0]);
563 vec[4] = float(up[1]);
564 vec[5] = float(up[2]);
565 alListenerfv(AL_ORIENTATION, vec);
566 }
567
568
569 std::string Sound::getPath(const std::string& name)
570 {
571 std::string path = Resource::getPath("sounds/" + name + ".ogg");
572 return path;
573 }
574
575
576 } // namespace Mf
577
578 /** vim: set ts=4 sw=4 tw=80: *************************************************/
579
This page took 0.053463 seconds and 3 git commands to generate.