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