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