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