]> Dogcows Code - chaz/yoink/blob - src/Moof/Sound.cc
dispatch class not a singleton, engine is static
[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 "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 "
100 << getName() << std::endl;
101 throw Error(Error::UNKNOWN_AUDIO_FORMAT, getName());
102 }
103
104 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
105 mFormat = getAudioFormat(vorbisInfo);
106 mFreq = vorbisInfo->rate;
107 }
108
109
110 void loadAll(ALuint source)
111 {
112 if (!mOggStream.datasource) openFile();
113 if (!mOggStream.datasource) return;
114
115 char data[BUFFER_SIZE];
116 int size = 0;
117
118 for (;;)
119 {
120 int section;
121 int result = ov_read(&mOggStream, data + size,
122 BUFFER_SIZE - size, 0, 2, 1, &section);
123
124 if (result > 0)
125 {
126 size += result;
127 }
128 else
129 {
130 if (result < 0) logWarning("vorbis playback error");
131 break;
132 }
133 }
134 if (size == 0)
135 {
136 logWarning << "decoded no bytes from "
137 << getName() << std::endl;
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 mIsLoaded = false;
210 mIsPlaying = false;
211 mIsLooping = false;
212
213 alGenSources(1, &mSource);
214
215 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
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 alSourcei(mSource, AL_LOOPING, mIsLooping);
222 }
223
224 ~Impl()
225 {
226 stop();
227
228 alDeleteSources(1, &mSource);
229
230 while (!mBuffers.empty())
231 {
232 alDeleteBuffers(1, &mBuffers.back());
233 mBuffers.pop_back();
234 }
235 }
236
237
238 void play()
239 {
240 if (mQueue.empty()) return;
241
242 if (!mIsLoaded) mQueue.front()->loadAll(mSource);
243
244 alSourcePlay(mSource);
245 mIsLoaded = true;
246 }
247
248
249 void playStream()
250 {
251 if (mQueue.empty()) return;
252
253 if (!mIsPlaying)
254 {
255 alSourcei(mSource, AL_LOOPING, false);
256 bufferStream();
257 }
258
259 if (!mStreamTimer.isValid())
260 {
261 mStreamTimer.init(boost::bind(&Impl::streamUpdate, this, _1, _2),
262 1.0, Timer::REPEAT);
263 }
264
265 alSourcePlay(mSource);
266 mIsPlaying = true;
267 }
268
269 void bufferStream()
270 {
271 ALuint buffer;
272 for (int i = mBuffers.size(); i <= 8; ++i)
273 {
274 alGenBuffers(1, &buffer);
275
276 if (mQueue.front()->stream(buffer))
277 {
278 alSourceQueueBuffers(mSource, 1, &buffer);
279 mBuffers.push_back(buffer);
280 }
281 else
282 {
283 alDeleteBuffers(1, &buffer);
284 break;
285 }
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 // queue up any unused buffers
322 bufferStream();
323 }
324 else if (mIsLooping)
325 {
326 // reload the same buffer
327 mQueue.push_back(buffer);
328 buffer->rewind();
329 buffer->stream(bufferObj);
330 alSourceQueueBuffers(mSource, 1, &bufferObj);
331 logInfo("looping same buffer");
332 }
333 else
334 {
335 // nothing more to play, stopping...
336 mIsPlaying = false;
337 std::remove(mBuffers.begin(), mBuffers.end(), bufferObj);
338 }
339 }
340 }
341
342 ALenum state;
343 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
344
345 // restart playing if we're stopped but supposed to be playing... this
346 // means we didn't queue enough and the audio skipped :-(
347 if (mIsPlaying && state != AL_PLAYING)
348 {
349 alSourcePlay(mSource);
350 }
351 }
352
353
354 void stop()
355 {
356 alSourceStop(mSource);
357 mIsPlaying = false;
358
359 mStreamTimer.invalidate();
360 }
361
362 void pause()
363 {
364 alSourcePause(mSource);
365 mIsPlaying = false;
366
367 mStreamTimer.invalidate();
368 }
369
370
371 void setSample(const std::string& name)
372 {
373 stop();
374 alSourcei(mSource, AL_BUFFER, AL_NONE);
375
376 mQueue.clear();
377 mIsLoaded = false;
378
379 enqueue(name);
380
381 while (!mBuffers.empty())
382 {
383 alDeleteBuffers(1, &mBuffers.back());
384 mBuffers.pop_back();
385 }
386 }
387
388 void enqueue(const std::string& name)
389 {
390 BufferP buffer = Buffer::getInstance(name);
391 mQueue.push_back(buffer);
392 }
393
394
395 bool isPlaying() const
396 {
397 if (mIsPlaying) return true;
398
399 ALenum state;
400 alGetSourcei(mSource, AL_SOURCE_STATE, &state);
401
402 return state == AL_PLAYING;
403 }
404
405
406 void setLooping(bool looping)
407 {
408 mIsLooping = looping;
409
410 ALenum type;
411 alGetSourcei(mSource, AL_SOURCE_TYPE, &type);
412
413 if (type != AL_STREAMING)
414 {
415 alSourcei(mSource, AL_LOOPING, mIsLooping);
416 }
417 }
418
419
420 void streamUpdate(Timer& timer, Scalar t)
421 {
422 // don't let the music die!
423 update();
424 // TODO - might be nice to also allow using threads for streaming rather
425 // than a timer, probably as a compile-time option
426 }
427
428
429 ALuint mSource;
430 std::list<ALuint> mBuffers;
431
432 bool mIsLoaded;
433 bool mIsPlaying;
434 bool mIsLooping;
435
436 std::deque<BufferP> mQueue;
437
438 Timer mStreamTimer;
439 };
440
441
442 Sound::Sound() :
443 // pass through
444 mImpl(new Sound::Impl) {}
445
446 Sound::Sound(const std::string& name) :
447 // pass through
448 mImpl(new Sound::Impl(name)) {}
449
450
451 void Sound::setSample(const std::string& name)
452 {
453 // pass through
454 mImpl->setSample(name);
455 }
456
457
458 void Sound::play()
459 {
460 // pass through
461 mImpl->play();
462 }
463
464 void Sound::stop()
465 {
466 // pass through
467 mImpl->stop();
468 }
469
470 void Sound::pause()
471 {
472 // pass through
473 mImpl->pause();
474 }
475
476
477 void Sound::toggle()
478 {
479 if (isPlaying()) pause();
480 else play();
481 }
482
483 bool Sound::isPlaying() const
484 {
485 // pass through
486 return mImpl->isPlaying();
487 }
488
489
490 void Sound::setPosition(const Vector3& position)
491 {
492 float vec[3] = {position[0], position[1], position[2]};
493 alSourcefv(mImpl->mSource, AL_POSITION, vec);
494 }
495
496 void Sound::setVelocity(const Vector3& velocity)
497 {
498 float vec[3] = {velocity[0], velocity[1], velocity[2]};
499 alSourcefv(mImpl->mSource, AL_VELOCITY, vec);
500 }
501
502 void Sound::setGain(Scalar gain)
503 {
504 alSourcef(mImpl->mSource, AL_GAIN, float(gain));
505 }
506
507 void Sound::setPitch(Scalar pitch)
508 {
509 alSourcef(mImpl->mSource, AL_PITCH, float(pitch));
510 }
511
512 void Sound::setLooping(bool looping)
513 {
514 // pass through
515 mImpl->setLooping(looping);
516 }
517
518
519 void Sound::setListenerPosition(const Vector3& position)
520 {
521 //alListener3f(AL_POSITION, float(position[0]), float(position[1]),
522 //float(position[2]));
523 float vec[] = {position[0], position[1], position[2]};
524 alListenerfv(AL_POSITION, vec);
525 }
526
527 void Sound::setListenerVelocity(const Vector3& velocity)
528 {
529 //alListener3f(AL_VELOCITY, float(velocity[0]), float(velocity[1]),
530 //float(velocity[2]));
531 float vec[] = {velocity[0], velocity[1], velocity[2]};
532 alListenerfv(AL_VELOCITY, vec);
533 }
534
535 void Sound::setListenerOrientation(const Vector3& forward, const Vector3& up)
536 {
537 float vec[6];
538 vec[0] = float(forward[0]);
539 vec[1] = float(forward[1]);
540 vec[2] = float(forward[2]);
541 vec[3] = float(up[0]);
542 vec[4] = float(up[1]);
543 vec[5] = float(up[2]);
544 alListenerfv(AL_ORIENTATION, vec);
545 }
546
547
548 std::string Sound::getPath(const std::string& name)
549 {
550 std::string path = Resource::getPath("sounds/" + name + ".ogg");
551 return path;
552 }
553
554
555 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
556
557
558 void SoundStream::enqueue(const std::string& name)
559 {
560 // pass through
561 mImpl->enqueue(name);
562 }
563
564
565 void SoundStream::play()
566 {
567 // pass through
568 mImpl->playStream();
569 }
570
571
572 } // namespace Mf
573
574 /** vim: set ts=4 sw=4 tw=80: *************************************************/
575
This page took 0.059144 seconds and 5 git commands to generate.