ray-scene intersection
[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 <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 mIsLoaded = false;
213 mIsPlaying = false;
214 mIsLooping = false;
215
216 alGenSources(1, &mSource);
217
218 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
219 alSourcef(mSource, AL_PITCH, 1.0f);
220 alSourcef(mSource, AL_GAIN, 1.0f);
221 alSourcefv(mSource, AL_POSITION, zero);
222 alSourcefv(mSource, AL_VELOCITY, zero);
223
224 alSourcei(mSource, AL_LOOPING, mIsLooping);
225 }
226
227 ~Impl()
228 {
229 stop();
230
231 alDeleteSources(1, &mSource);
232
233 while (!mBuffers.empty())
234 {
235 alDeleteBuffers(1, &mBuffers.back());
236 mBuffers.pop_back();
237 }
238 }
239
240
241 void play()
242 {
243 if (mQueue.empty()) return;
244
245 if (!mIsLoaded) mQueue.front()->loadAll(mSource);
246
247 alSourcePlay(mSource);
248 mIsLoaded = true;
249 }
250
251
252 void playStream()
253 {
254 if (mQueue.empty()) return;
255
256 if (!mIsPlaying)
257 {
258 alSourcei(mSource, AL_LOOPING, false);
259 bufferStream();
260 }
261
262 if (!mStreamTimer.isValid())
263 {
264 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
265 1.0, Timer::REPEAT);
266 }
267
268 alSourcePlay(mSource);
269 mIsPlaying = true;
270 }
271
272 void bufferStream()
273 {
274 ALuint buffer;
275 for (int i = mBuffers.size(); i <= 8; ++i)
276 {
277 alGenBuffers(1, &buffer);
278
279 if (mQueue.front()->stream(buffer))
280 {
281 alSourceQueueBuffers(mSource, 1, &buffer);
282 mBuffers.push_back(buffer);
283 }
284 else
285 {
286 alDeleteBuffers(1, &buffer);
287 break;
288 }
289 }
290 }
291
292
293 void update()
294 {
295 ALint finished = 0;
296
297 alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished);
298
299 while (finished-- > 0)
300 {
301 ALuint bufferObj;
302 alSourceUnqueueBuffers(mSource, 1, &bufferObj);
303
304 BufferP buffer = mQueue.front();
305 bool streamed = buffer->stream(bufferObj);
306
307 if (streamed)
308 {
309 alSourceQueueBuffers(mSource, 1, &bufferObj);
310 }
311 else
312 {
313 // the buffer couldn't be streamed, so get rid of it
314 mQueue.pop_front();
315
316 if (!mQueue.empty())
317 {
318 // begin the next buffer in the queue
319 mQueue.front()->rewind();
320 mQueue.front()->stream(bufferObj);
321 alSourceQueueBuffers(mSource, 1, &bufferObj);
322 logInfo("loading new buffer");
323
324 // queue up any unused buffers
325 bufferStream();
326 }
327 else if (mIsLooping)
328 {
329 // reload the same buffer
330 mQueue.push_back(buffer);
331 buffer->rewind();
332 buffer->stream(bufferObj);
333 alSourceQueueBuffers(mSource, 1, &bufferObj);
334 logInfo("looping same buffer");
335 }
336 else
337 {
338 // nothing more to play, stopping...
339 mIsPlaying = false;
340 std::remove(mBuffers.begin(), mBuffers.end(), bufferObj);
341 }
342 }
343 }
344
345 ALenum state;
346 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
347
348 // restart playing if we're stopped but supposed to be playing... this
349 // means we didn't queue enough and the audio skipped :-(
350 if (mIsPlaying && state != AL_PLAYING)
351 {
352 alSourcePlay(mSource);
353 }
354 }
355
356
357 void stop()
358 {
359 alSourceStop(mSource);
360 mIsPlaying = false;
361
362 mStreamTimer.invalidate();
363 }
364
365 void pause()
366 {
367 alSourcePause(mSource);
368 mIsPlaying = false;
369
370 mStreamTimer.invalidate();
371 }
372
373
374 void setSample(const std::string& name)
375 {
376 stop();
377 alSourcei(mSource, AL_BUFFER, AL_NONE);
378
379 mQueue.clear();
380 mIsLoaded = false;
381
382 enqueue(name);
383
384 while (!mBuffers.empty())
385 {
386 alDeleteBuffers(1, &mBuffers.back());
387 mBuffers.pop_back();
388 }
389 }
390
391 void enqueue(const std::string& name)
392 {
393 BufferP buffer = Buffer::getInstance(name);
394 mQueue.push_back(buffer);
395 }
396
397
398 bool isPlaying() const
399 {
400 if (mIsPlaying) return true;
401
402 ALenum state;
403 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
404
405 return state == AL_PLAYING;
406 }
407
408
409 void setLooping(bool looping)
410 {
411 mIsLooping = looping;
412
413 ALenum type;
414 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
415
416 if (type != AL_STREAMING)
417 {
418 alSourcei(mSource, AL_LOOPING, mIsLooping);
419 }
420 }
421
422
423 void streamUpdate(Timer& timer, Scalar t)
424 {
425 // don't let the music die!
426 update();
427 // TODO - might be nice to also allow using threads for streaming rather
428 // than a timer, probably as a compile-time option
429 }
430
431
432 ALuint mSource;
433 std::list<ALuint> mBuffers;
434
435 bool mIsLoaded;
436 bool mIsPlaying;
437 bool mIsLooping;
438
439 std::deque<BufferP> mQueue;
440
441 Timer mStreamTimer;
442 };
443
444
445 Sound::Sound() :
446 // pass through
447 mImpl(new Sound::Impl) {}
448
449 Sound::Sound(const std::string& name) :
450 // pass through
451 mImpl(new Sound::Impl(name)) {}
452
453
454 void Sound::setSample(const std::string& name)
455 {
456 // pass through
457 mImpl->setSample(name);
458 }
459
460
461 void Sound::play()
462 {
463 // pass through
464 mImpl->play();
465 }
466
467 void Sound::stop()
468 {
469 // pass through
470 mImpl->stop();
471 }
472
473 void Sound::pause()
474 {
475 // pass through
476 mImpl->pause();
477 }
478
479
480 void Sound::toggle()
481 {
482 if (isPlaying()) pause();
483 else play();
484 }
485
486 bool Sound::isPlaying() const
487 {
488 // pass through
489 return mImpl->isPlaying();
490 }
491
492
493 void Sound::setPosition(const Vector3& position)
494 {
495 float vec[3] = {position[0], position[1], position[2]};
496 alSourcefv(mImpl->mSource, AL_POSITION, vec);
497 }
498
499 void Sound::setVelocity(const Vector3& velocity)
500 {
501 float vec[3] = {velocity[0], velocity[1], velocity[2]};
502 alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
503 }
504
505 void Sound::setGain(Scalar gain)
506 {
507 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
508 }
509
510 void Sound::setPitch(Scalar pitch)
511 {
512 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
513 }
514
515 void Sound::setLooping(bool looping)
516 {
517 // pass through
518 mImpl->setLooping(looping);
519 }
520
521
522 void Sound::setListenerPosition(const Vector3& position)
523 {
524 //alListener3f(AL_POSITION, float(position[0]), float(position[1]),
525 //float(position[2]));
526 float vec[] = {position[0], position[1], position[2]};
527 alListenerfv(AL_POSITION, vec);
528 }
529
530 void Sound::setListenerVelocity(const Vector3& velocity)
531 {
532 //alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
533 //float(velocity[2]));
534 float vec[] = {velocity[0], velocity[1], velocity[2]};
535 alListenerfv(AL_VELOCITY, vec);
536 }
537
538 void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
539 {
540 float vec[6];
541 vec[0] = float(forward[0]);
542 vec[1] = float(forward[1]);
543 vec[2] = float(forward[2]);
544 vec[3] = float(up[0]);
545 vec[4] = float(up[1]);
546 vec[5] = float(up[2]);
547 alListenerfv(AL_ORIENTATION, vec);
548 }
549
550
551 std::string Sound::getPath(const std::string& name)
552 {
553 std::string path = Resource::getPath("sounds/" + name + ".ogg");
554 return path;
555 }
556
557
558 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
559
560
561 void SoundStream::enqueue(const std::string& name)
562 {
563 // pass through
564 mImpl->enqueue(name);
565 }
566
567
568 void SoundStream::play()
569 {
570 // pass through
571 mImpl->playStream();
572 }
573
574
575 } // namespace Mf
576
577 /** vim: set ts=4 sw=4 tw=80: *************************************************/
578
This page took 0.055146 seconds and 4 git commands to generate.