]> Dogcows Code - chaz/yoink/blob - src/Moof/Sound.cc
a6156249add8e0043c2b4f3637336053e4acb574
[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 "Exception.hh"
39 #include "Library.hh"
40 #include "Log.hh"
41 #include "Sound.hh"
42 #include "Timer.hh"
43
44 #define BUFFER_SIZE (64 * 1024)
45 //#define BUFFER_SIZE (5*2048)
46
47 namespace Mf {
48
49
50 class Sound::Impl
51 {
52 public:
53
54 static ALenum getAudioFormat(const vorbis_info* audioInfo)
55 {
56 if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
57 else return AL_FORMAT_STEREO16;
58 }
59
60
61 class Buffer;
62 typedef boost::shared_ptr<Buffer> BufferP;
63
64 class Buffer : public Library<Buffer>
65 {
66 public:
67
68 Buffer(const std::string& name) :
69 Library<Buffer>(name),
70 mBuffer(-1)
71 {
72 mOggStream.datasource = 0;
73 openFile();
74 }
75
76 ~Buffer()
77 {
78 if (mOggStream.datasource)
79 {
80 ov_clear(&mOggStream);
81 }
82 if (int(mBuffer) != -1) alDeleteBuffers(1, &mBuffer);
83 }
84
85
86 void openFile()
87 {
88 if (mOggStream.datasource)
89 {
90 ov_clear(&mOggStream);
91 mOggStream.datasource = 0;
92 }
93
94 std::string filePath = Sound::getPath(getName());
95 int result = ov_fopen((char*)filePath.c_str(), &mOggStream);
96
97 if (result < 0)
98 {
99 logWarning("error while loading sound %s",
100 getName().c_str());
101 throw Exception(ErrorCode::UNKNOWN_AUDIO_FORMAT);
102 }
103
104 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
105 mFormat = getAudioFormat(vorbisInfo);
106 mFreq = vorbisInfo->rate;
107
108 logDebug(" channels: %d", vorbisInfo->channels);
109 logDebug(" frequency: %d", vorbisInfo->rate);
110 }
111
112
113 void loadAll(ALuint source)
114 {
115 if (!mOggStream.datasource) openFile();
116 if (!mOggStream.datasource) return;
117
118 char data[BUFFER_SIZE];
119 int size = 0;
120
121 for (;;)
122 {
123 int section;
124 int result = ov_read(&mOggStream, data + size,
125 BUFFER_SIZE - size, 0, 2, 1, &section);
126
127 if (result > 0)
128 {
129 size += result;
130 }
131 else
132 {
133 if (result < 0) logWarning("vorbis playback error");
134 break;
135 }
136 }
137 if (size == 0)
138 {
139 logWarning("decoded no bytes from %s", getName().c_str());
140 //throw Exception("file_not_found");
141 return;
142 }
143
144 alGenBuffers(1, &mBuffer);
145
146 alBufferData(mBuffer, mFormat, data, size, mFreq);
147 alSourcei(source, AL_BUFFER, mBuffer);
148
149 // don't need to keep this loaded
150 ov_clear(&mOggStream);
151 mOggStream.datasource = 0;
152 }
153
154 bool stream(ALuint buffer)
155 {
156 char data[BUFFER_SIZE];
157 int size = 0;
158
159 while (size < BUFFER_SIZE)
160 {
161 int section;
162 int result = ov_read(&mOggStream, data + size,
163 BUFFER_SIZE - size, 0, 2, 1, &section);
164
165 if (result > 0)
166 {
167 size += result;
168 }
169 else
170 {
171 if (result < 0) logWarning("vorbis playback error");
172 break;
173 }
174 }
175
176 if (size == 0) return false;
177
178 alBufferData(buffer, mFormat, data, size, mFreq);
179
180 return true;
181 }
182
183 void rewind()
184 {
185 if (!mOggStream.datasource) openFile();
186 else ov_raw_seek(&mOggStream, 0);
187 }
188
189
190 private:
191
192 OggVorbis_File mOggStream;
193 ALenum mFormat;
194 ALsizei mFreq;
195 ALuint mBuffer;
196 };
197
198
199 Impl()
200 {
201 init();
202 }
203
204 Impl(const std::string& name)
205 {
206 init();
207 enqueue(name);
208 }
209
210 void init()
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 < 4; ++i)
277 {
278 alGenBuffers(1, &buffer);
279 mBufferObjects.push_back(buffer);
280 }
281 for (int i = 0; i < 4; ++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.055169 seconds and 3 git commands to generate.