]> Dogcows Code - chaz/yoink/blob - src/Moof/Sound.cc
minor refactoring and state progress
[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 <string>
31 #include <queue>
32 #include <vector>
33
34 #include <AL/al.h>
35 #include <vorbis/codec.h>
36 #include <vorbis/vorbisfile.h>
37
38 #include "Log.hh"
39 #include "Mippleton.hh"
40 #include "Sound.hh"
41 #include "Timer.hh"
42
43 #define BUFFER_SIZE (64 * 1024)
44 //#define BUFFER_SIZE (5*2048)
45
46 namespace Mf {
47
48
49 class Sound::Impl
50 {
51 public:
52
53 static ALenum getAudioFormat(const vorbis_info* audioInfo)
54 {
55 if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
56 else return AL_FORMAT_STEREO16;
57 }
58
59
60 class Buffer;
61 typedef boost::shared_ptr<Buffer> BufferP;
62
63 class Buffer : public Mippleton<Buffer>
64 {
65 OggVorbis_File mOggStream;
66 ALenum mFormat;
67 ALsizei mFreq;
68 std::vector<ALuint> mObjects;
69
70 public:
71
72 Buffer(const std::string& name) :
73 Mippleton<Buffer>(name)
74 {
75 mOggStream.datasource = 0;
76 openFile();
77 }
78
79 ~Buffer()
80 {
81 while (!mObjects.empty())
82 {
83 alDeleteBuffers(1, &mObjects.back());
84 mObjects.pop_back();
85 }
86
87 if (mOggStream.datasource)
88 {
89 ov_clear(&mOggStream);
90 }
91 }
92
93
94 void openFile()
95 {
96 if (mOggStream.datasource)
97 {
98 ov_clear(&mOggStream);
99 mOggStream.datasource = 0;
100 }
101
102 std::string filePath = Sound::getPath(getName());
103 int result = ov_fopen((char*)filePath.c_str(), &mOggStream);
104
105 if (result < 0)
106 {
107 logWarning("error while loading sound %s",
108 getName().c_str());
109 throw Exception(Exception::BAD_AUDIO_FORMAT);
110 }
111
112 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
113 mFormat = getAudioFormat(vorbisInfo);
114 mFreq = vorbisInfo->rate;
115
116 logDebug(" channels: %d", vorbisInfo->channels);
117 logDebug(" frequency: %d", vorbisInfo->rate);
118 }
119
120
121 void loadAll(ALuint source)
122 {
123 if (!mOggStream.datasource) openFile();
124 if (!mOggStream.datasource) return;
125
126 char data[BUFFER_SIZE];
127 int size = 0;
128
129 for (;;)
130 {
131 int section;
132 int result = ov_read(&mOggStream, data + size,
133 BUFFER_SIZE - size, 0, 2, 1, &section);
134
135 if (result > 0)
136 {
137 size += result;
138 }
139 else
140 {
141 if (result < 0) logWarning("vorbis playback error");
142 break;
143 }
144 }
145 if (size == 0)
146 {
147 logWarning("decoded no bytes from %s", getName().c_str());
148 //throw Exception(Exception::FILE_NOT_FOUND);
149 return;
150 }
151
152 ALuint obj;
153 alGenBuffers(1, &obj);
154
155 alBufferData(obj, mFormat, data, size, mFreq);
156
157 mObjects.push_back(obj);
158
159 alSourcei(source, AL_BUFFER, obj);
160
161 // don't need this anymore
162 ov_clear(&mOggStream);
163 mOggStream.datasource = 0;
164 }
165
166
167 void beginStream(ALuint source, int nBuffers = 8)
168 {
169 if (!mOggStream.datasource) openFile();
170 if (!mOggStream.datasource) return;
171
172 ALuint objs[nBuffers];
173 alGenBuffers(nBuffers, objs);
174
175 for (int i = 0; i < nBuffers; ++i)
176 {
177 mObjects.push_back(objs[i]);
178 stream(objs[i]);
179 }
180
181 alSourceQueueBuffers(source, nBuffers, objs);
182 }
183
184 enum StreamStatus
185 {
186 STREAM_OK = 0,
187 STREAM_EOF = 1,
188 STREAM_WRONG = 2
189 };
190
191 StreamStatus stream(ALuint buffer)
192 {
193 std::vector<ALuint>::iterator it =
194 std::find(mObjects.begin(), mObjects.end(), buffer);
195
196 // that buffer doesn't belong to us
197 if (it == mObjects.end()) return STREAM_WRONG;
198
199 char data[BUFFER_SIZE];
200 int size = 0;
201
202 while (size < BUFFER_SIZE)
203 {
204 int section;
205 int result = ov_read(&mOggStream, data + size,
206 BUFFER_SIZE - size, 0, 2, 1, &section);
207
208 if (result > 0)
209 {
210 size += result;
211 }
212 else
213 {
214 if (result < 0) logWarning("vorbis playback error");
215 break;
216 }
217 }
218
219 if (size == 0) return STREAM_EOF;
220
221 alBufferData(buffer, mFormat, data, size, mFreq);
222
223 return STREAM_OK;
224 }
225
226 inline void rewind()
227 {
228 if (!mOggStream.datasource) openFile();
229 else ov_raw_seek(&mOggStream, 0);
230 }
231
232
233 // delete unused buffers, return true if all buffers deleted
234 inline bool clear()
235 {
236 // clear any openal errors
237 alGetError();
238
239 while (!mObjects.empty())
240 {
241 ALuint buffer = mObjects.back();
242 alDeleteBuffers(1, &buffer);
243
244 // if an error occured, the buffer was not deleted because it's
245 // still in use by some source
246 if (alGetError() != AL_NO_ERROR) return false;
247
248 mObjects.pop_back();
249 }
250
251 return true;
252 }
253 };
254
255
256 Impl(const std::string& name) :
257 mBuffer(Buffer::getInstance(name)),
258 mIsPlaying(false),
259 mIsLooping(false)
260 {
261 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
262
263 alGenSources(1, &mSource);
264
265 alSourcef(mSource, AL_PITCH, 1.0f);
266 alSourcef(mSource, AL_GAIN, 1.0f);
267 alSourcefv(mSource, AL_POSITION, zero);
268 alSourcefv(mSource, AL_VELOCITY, zero);
269 }
270
271 ~Impl()
272 {
273 alDeleteSources(1, &mSource);
274 }
275
276
277 void play()
278 {
279 ALenum type;
280 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
281
282 if (type != AL_STATIC)
283 {
284 mBuffer->loadAll(mSource);
285 }
286
287 alSourcei(mSource, AL_LOOPING, mIsLooping);
288 alSourcePlay(mSource);
289 mIsPlaying = true;
290 }
291
292
293 void stream()
294 {
295 ALenum type;
296 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
297
298 alSourcei(mSource, AL_BUFFER, AL_NONE);
299 mBuffer->rewind();
300 mBuffer->beginStream(mSource);
301
302 alSourcei(mSource, AL_LOOPING, AL_FALSE);
303 alSourcePlay(mSource);
304 mIsPlaying = true;
305
306 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2), 1.0,
307 Timer::REPEAT);
308 }
309
310 inline void update()
311 {
312 ALint finished = 0;
313
314 alGetSourcei(mSource, AL_BUFFERS_PROCESSED, &finished);
315
316 while (finished-- > 0)
317 {
318 ALuint buffer;
319
320 alSourceUnqueueBuffers(mSource, 1, &buffer);
321
322 Buffer::StreamStatus status = mBuffer->stream(buffer);
323
324 if (status == Buffer::STREAM_OK)
325 {
326 alSourceQueueBuffers(mSource, 1, &buffer);
327 }
328 else if (status == Buffer::STREAM_EOF)
329 {
330 if (!mQueue.empty())
331 {
332 // begin the next buffer in the queue
333 mExpired.push_back(mBuffer);
334 mBuffer = mQueue.front();
335 mQueue.pop();
336 mBuffer->beginStream(mSource, 1);
337 }
338 else if (mIsLooping)
339 {
340 // restart from the beginning
341 mBuffer->rewind();
342 mBuffer->stream(buffer);
343 alSourceQueueBuffers(mSource, 1, &buffer);
344 }
345 }
346 else if (status == Buffer::STREAM_WRONG)
347 {
348 clear();
349 mBuffer->beginStream(mSource, 1);
350 }
351 }
352
353 ALenum state;
354 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
355
356 // restart playing if we're stopped but supposed to be playing... this
357 // means we didn't queue enough and the audio skipped
358 if (mIsPlaying && state != AL_PLAYING)
359 {
360 alSourcePlay(mSource);
361 }
362 }
363
364 inline void clear()
365 {
366 // try to remove expired buffers
367 std::vector<BufferP>::iterator it;
368 for (it = mExpired.end() - 1; it >= mExpired.begin(); --it)
369 {
370 if ((*it)->clear()) mExpired.erase(it);
371 }
372 }
373
374
375 void stop()
376 {
377 alSourceStop(mSource);
378 mIsPlaying = false;
379 }
380
381 inline void pause()
382 {
383 alSourcePause(mSource);
384 mIsPlaying = false;
385 }
386
387 inline void resume()
388 {
389 alSourcePlay(mSource);
390 mIsPlaying = true;
391 }
392
393
394 inline void setSample(const std::string& name)
395 {
396 bool playing = isPlaying();
397 ALenum type;
398 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
399
400 stop();
401
402 //alSourcei(mSource, AL_BUFFER, AL_NONE);
403 mBuffer = Buffer::getInstance(name);
404
405 if (type == AL_STREAMING)
406 {
407 if (playing) stream();
408 }
409 else
410 {
411 if (playing) play();
412 }
413 }
414
415 inline void enqueue(const std::string& name)
416 {
417 BufferP buffer = Buffer::getInstance(name);
418 mQueue.push(buffer);
419 }
420
421
422 inline bool isPlaying() const
423 {
424 if (mIsPlaying) return true;
425
426 ALenum state;
427 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
428
429 return state == AL_PLAYING;
430 }
431
432
433 inline void setLooping(bool looping)
434 {
435 mIsLooping = looping;
436
437 ALenum type;
438 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
439
440 if (type != AL_STREAMING)
441 {
442 alSourcei(mSource, AL_LOOPING, mIsLooping);
443 }
444 }
445
446
447 ALuint mSource;
448 BufferP mBuffer;
449
450 bool mIsPlaying;
451 bool mIsLooping;
452
453 std::queue<BufferP> mQueue;
454 std::vector<BufferP> mExpired;
455
456 Timer mStreamTimer;
457
458 void streamUpdate(Timer& timer, Scalar t)
459 {
460 // don't let the music die!
461 update();
462 // TODO - might be nice to also allow using threads for streaming rather
463 // than a timer, probably as a compile-time option
464 }
465 };
466
467 Sound::Sound(const std::string& name) :
468 // pass through
469 mImpl(new Sound::Impl(name)) {}
470
471
472 void Sound::play()
473 {
474 // pass through
475 mImpl->play();
476 }
477
478 void Sound::stream()
479 {
480 // pass through
481 mImpl->stream();
482 }
483
484
485 void Sound::stop()
486 {
487 // pass through
488 mImpl->stop();
489 }
490
491 void Sound::pause()
492 {
493 // pass through
494 mImpl->pause();
495 }
496
497 void Sound::resume()
498 {
499 // pass through
500 mImpl->resume();
501 }
502
503 void Sound::toggle()
504 {
505 if (mImpl->mIsPlaying) pause();
506 else resume();
507 }
508
509
510 void Sound::setSample(const std::string& name)
511 {
512 // pass through
513 mImpl->setSample(name);
514 }
515
516 void Sound::enqueue(const std::string& name)
517 {
518 // pass through
519 mImpl->enqueue(name);
520 }
521
522
523 bool Sound::isPlaying() const
524 {
525 // pass through
526 return mImpl->isPlaying();
527 }
528
529 void Sound::setPosition(const Vector3& position)
530 {
531 float p[3] = {position[0], position[1], position[2]};
532 alSourcefv(mImpl->mSource, AL_POSITION, p);
533 }
534
535 void Sound::setVelocity(const Vector3& velocity)
536 {
537 float v[3] = {velocity[0], velocity[1], velocity[2]};
538 alSourcefv(mImpl->mSource, AL_VELOCITY, v);
539 }
540
541 void Sound::setGain(Scalar gain)
542 {
543 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
544 }
545
546 void Sound::setPitch(Scalar pitch)
547 {
548 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
549 }
550
551 void Sound::setLooping(bool looping)
552 {
553 // pass through
554 mImpl->setLooping(looping);
555 }
556
557
558 void Sound::setListenerPosition(const Vector3& position)
559 {
560 alListener3f(AL_POSITION, float(position[0]), float(position[1]),
561 float(position[2]));
562 }
563
564 void Sound::setListenerVelocity(const Vector3& velocity)
565 {
566 alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
567 float(velocity[2]));
568 }
569
570 void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
571 {
572 float vec[6];
573 vec[0] = float(forward[0]);
574 vec[1] = float(forward[1]);
575 vec[2] = float(forward[2]);
576 vec[3] = float(up[0]);
577 vec[4] = float(up[1]);
578 vec[5] = float(up[2]);
579 alListenerfv(AL_ORIENTATION, vec);
580 }
581
582
583 std::string Sound::getPath(const std::string& name)
584 {
585 std::string path = Resource::getPath("sounds/" + name + ".ogg");
586 return path;
587 }
588
589
590 } // namespace Mf
591
592 /** vim: set ts=4 sw=4 tw=80: *************************************************/
593
This page took 0.05558 seconds and 5 git commands to generate.