]> Dogcows Code - chaz/yoink/blob - src/Moof/Sound.cc
b91158a591e318fae4dba1e0ac03aa8ca5871e72
[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 "
101 << getName() << std::endl;
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 "
138 << getName() << std::endl;
139 return;
140 }
141
142 alGenBuffers(1, &mBuffer);
143
144 alBufferData(mBuffer, mFormat, data, size, mFreq);
145 alSourcei(source, AL_BUFFER, mBuffer);
146
147 // don't need to keep this loaded
148 ov_clear(&mOggStream);
149 mOggStream.datasource = 0;
150 }
151
152 bool stream(ALuint buffer)
153 {
154 char data[BUFFER_SIZE];
155 int size = 0;
156
157 while (size < BUFFER_SIZE)
158 {
159 int section;
160 int result = ov_read(&mOggStream, data + size,
161 BUFFER_SIZE - size, 0, 2, 1, &section);
162
163 if (result > 0)
164 {
165 size += result;
166 }
167 else
168 {
169 if (result < 0) logWarning("vorbis playback error");
170 break;
171 }
172 }
173
174 if (size == 0) return false;
175
176 alBufferData(buffer, mFormat, data, size, mFreq);
177
178 return true;
179 }
180
181 void rewind()
182 {
183 if (!mOggStream.datasource) openFile();
184 else ov_raw_seek(&mOggStream, 0);
185 }
186
187
188 private:
189
190 OggVorbis_File mOggStream;
191 ALenum mFormat;
192 ALsizei mFreq;
193 ALuint mBuffer;
194 };
195
196
197 Impl()
198 {
199 init();
200 }
201
202 Impl(const std::string& name)
203 {
204 init();
205 enqueue(name);
206 }
207
208 void init()
209 {
210 // make sure the engine is initialized
211 Engine::getInstance();
212
213 mIsLoaded = false;
214 mIsPlaying = false;
215 mIsLooping = false;
216
217 alGenSources(1, &mSource);
218
219 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
220 alSourcef(mSource, AL_PITCH, 1.0f);
221 alSourcef(mSource, AL_GAIN, 1.0f);
222 alSourcefv(mSource, AL_POSITION, zero);
223 alSourcefv(mSource, AL_VELOCITY, zero);
224
225 alSourcei(mSource, AL_LOOPING, mIsLooping);
226 }
227
228 ~Impl()
229 {
230 stop();
231
232 alDeleteSources(1, &mSource);
233
234 while (!mBuffers.empty())
235 {
236 alDeleteBuffers(1, &mBuffers.back());
237 mBuffers.pop_back();
238 }
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
433 ALuint mSource;
434 std::list<ALuint> mBuffers;
435
436 bool mIsLoaded;
437 bool mIsPlaying;
438 bool mIsLooping;
439
440 std::deque<BufferP> mQueue;
441
442 Timer mStreamTimer;
443 };
444
445
446 Sound::Sound() :
447 // pass through
448 mImpl(new Sound::Impl) {}
449
450 Sound::Sound(const std::string& name) :
451 // pass through
452 mImpl(new Sound::Impl(name)) {}
453
454
455 void Sound::setSample(const std::string& name)
456 {
457 // pass through
458 mImpl->setSample(name);
459 }
460
461
462 void Sound::play()
463 {
464 // pass through
465 mImpl->play();
466 }
467
468 void Sound::stop()
469 {
470 // pass through
471 mImpl->stop();
472 }
473
474 void Sound::pause()
475 {
476 // pass through
477 mImpl->pause();
478 }
479
480
481 void Sound::toggle()
482 {
483 if (isPlaying()) pause();
484 else play();
485 }
486
487 bool Sound::isPlaying() const
488 {
489 // pass through
490 return mImpl->isPlaying();
491 }
492
493
494 void Sound::setPosition(const Vector3& position)
495 {
496 float vec[3] = {position[0], position[1], position[2]};
497 alSourcefv(mImpl->mSource, AL_POSITION, vec);
498 }
499
500 void Sound::setVelocity(const Vector3& velocity)
501 {
502 float vec[3] = {velocity[0], velocity[1], velocity[2]};
503 alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
504 }
505
506 void Sound::setGain(Scalar gain)
507 {
508 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
509 }
510
511 void Sound::setPitch(Scalar pitch)
512 {
513 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
514 }
515
516 void Sound::setLooping(bool looping)
517 {
518 // pass through
519 mImpl->setLooping(looping);
520 }
521
522
523 void Sound::setListenerPosition(const Vector3& position)
524 {
525 //alListener3f(AL_POSITION, float(position[0]), float(position[1]),
526 //float(position[2]));
527 float vec[] = {position[0], position[1], position[2]};
528 alListenerfv(AL_POSITION, vec);
529 }
530
531 void Sound::setListenerVelocity(const Vector3& velocity)
532 {
533 //alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
534 //float(velocity[2]));
535 float vec[] = {velocity[0], velocity[1], velocity[2]};
536 alListenerfv(AL_VELOCITY, vec);
537 }
538
539 void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
540 {
541 float vec[6];
542 vec[0] = float(forward[0]);
543 vec[1] = float(forward[1]);
544 vec[2] = float(forward[2]);
545 vec[3] = float(up[0]);
546 vec[4] = float(up[1]);
547 vec[5] = float(up[2]);
548 alListenerfv(AL_ORIENTATION, vec);
549 }
550
551
552 std::string Sound::getPath(const std::string& name)
553 {
554 std::string path = Resource::getPath("sounds/" + name + ".ogg");
555 return path;
556 }
557
558
559 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
560
561
562 void SoundStream::enqueue(const std::string& name)
563 {
564 // pass through
565 mImpl->enqueue(name);
566 }
567
568
569 void SoundStream::play()
570 {
571 // pass through
572 mImpl->playStream();
573 }
574
575
576 } // namespace Mf
577
578 /** vim: set ts=4 sw=4 tw=80: *************************************************/
579
This page took 0.050914 seconds and 3 git commands to generate.