]> Dogcows Code - chaz/yoink/blob - src/moof/sound.cc
fixed documentation about where to find licenses
[chaz/yoink] / src / moof / sound.cc
1
2 /*] Copyright (c) 2009-2011, Charles McGarvey [*****************************
3 **] All rights reserved.
4 *
5 * Distributable under the terms and conditions of the 2-clause BSD license;
6 * see the file COPYING for a complete text of the license.
7 *
8 *****************************************************************************/
9
10 #include <deque>
11 #include <stdexcept>
12
13 #include <boost/algorithm/string.hpp>
14 #include <boost/cstdint.hpp>
15 #include <boost/noncopyable.hpp>
16 #include <AL/al.h>
17 #include <AL/alc.h>
18 #include <vorbis/codec.h>
19 #include <vorbis/vorbisfile.h>
20
21 #include "debug.hh"
22 #include "sound.hh"
23 #include "resource.hh"
24 #include "runloop.hh"
25 #include "thread.hh"
26 #include "timer.hh"
27
28
29 #ifndef BUF_SIZE
30 #define BUF_SIZE 8192
31 #endif
32
33 #define NUM_SAMPLE_BITS 16
34
35
36 namespace moof {
37
38
39 thread stream_thread_;
40
41
42 class sound_backend
43 {
44 public:
45
46 sound_backend()
47 {
48 if (retain_count++ == 0)
49 {
50 al_device = alcOpenDevice(0);
51 al_context = alcCreateContext(al_device, 0);
52 if (!al_device || !al_context)
53 {
54 const char* error = alcGetString(al_device,
55 alcGetError(al_device));
56 log_error("audio subsystem initialization failure", error);
57 }
58 else
59 {
60 alcMakeContextCurrent(al_context);
61 log_info << "opened sound device `" <<
62 alcGetString(al_device, ALC_DEFAULT_DEVICE_SPECIFIER) <<
63 "'" << std::endl;
64 }
65 }
66 }
67
68 sound_backend(const sound_backend& backend)
69 {
70 ++retain_count;
71 }
72
73 sound_backend& operator = (const sound_backend& backend)
74 {
75 ++retain_count;
76 return *this;
77 }
78
79 ~sound_backend()
80 {
81 if (--retain_count == 0)
82 {
83 alcMakeContextCurrent(0);
84 alcDestroyContext(al_context);
85 alcCloseDevice(al_device);
86 }
87 }
88
89 static int retain_count;
90 static ALCdevice* al_device;
91 static ALCcontext* al_context;
92 };
93
94 int sound_backend::retain_count = 0;
95 ALCdevice* sound_backend::al_device;
96 ALCcontext* sound_backend::al_context;
97
98
99 class sound_resource;
100 typedef resource_handle<sound_resource> sound_handle;
101
102 MOOF_REGISTER_RESOURCE(sound_resource, ogg, sounds);
103
104 class sound_resource : public boost::noncopyable
105 {
106 public:
107
108 sound_resource(const std::string& path) :
109 buffer_(AL_NONE),
110 sample_(0)
111 {
112 if (ov_fopen((char*)path.c_str(), &file_) < 0)
113 throw std::runtime_error("problem reading audio: " +
114 path);
115 }
116
117 ~sound_resource()
118 {
119 ov_clear(&file_);
120 if (buffer_) alDeleteBuffers(1, &buffer_);
121 }
122
123 ALuint read_all() const
124 {
125 if (buffer_) return buffer_;
126
127 if (ov_pcm_seek(&file_, 0) != 0)
128 {
129 log_warning("vorbis seek error");
130 return AL_NONE;
131 }
132
133 ogg_int64_t samples = ov_pcm_total(&file_, 0);
134
135 char data[2 * samples * (NUM_SAMPLE_BITS / 8)];
136 size_t size = 0;
137
138 while (size < sizeof(data))
139 {
140 int section;
141 int result = ov_read(&file_, data + size,
142 sizeof(data) - size, 0, 2, 1, &section);
143
144 if (0 < result)
145 {
146 size += result;
147 }
148 else if (result == 0 && size > 0)
149 {
150 vorbis_info* info = ov_info(&file_, section);
151
152 ALuint buffer;
153 alGenBuffers(1, &buffer);
154 alBufferData(buffer, get_audio_format(info),
155 data, size, info->rate);
156
157 buffer_ = buffer;
158 return buffer;
159 }
160 else
161 {
162 log_warning("vorbis playback error");
163 break;
164 }
165 }
166 return AL_NONE;
167 }
168
169 bool read(ALuint buffer, uint64_t& sample) const
170 {
171 if ((sample == sample_ && ov_pcm_seek_lap(&file_, sample) != 0) ||
172 (sample != sample_ && ov_pcm_seek(&file_, sample) != 0))
173 return false;
174
175 char data[BUF_SIZE];
176 int section;
177 int result = ov_read(&file_, data, sizeof(data),
178 0, 2, 1, &section);
179
180 if (0 < result)
181 {
182 vorbis_info* info = ov_info(&file_, section);
183 alBufferData(buffer, get_audio_format(info),
184 data, result, info->rate);
185 sample_ = sample = ov_pcm_tell(&file_);
186 return true;
187 }
188 else if (result < 0)
189 {
190 log_warning("vorbis playback error");
191 }
192 return false;
193 }
194
195 bool read(ALuint buffer, uint64_t& sample, sound_handle other) const
196 {
197 ov_crosslap(&other->file_, &file_);
198
199 char data[BUF_SIZE];
200 int section;
201 int result = ov_read(&file_, data, sizeof(data),
202 0, 2, 1, &section);
203
204 if (0 < result)
205 {
206 vorbis_info* info = ov_info(&file_, section);
207 alBufferData(buffer, get_audio_format(info),
208 data, result, info->rate);
209 sample_ = sample = ov_pcm_tell(&file_);
210 return true;
211 }
212 else if (result < 0)
213 {
214 log_warning("vorbis playback error");
215 }
216 return false;
217 }
218
219 // determines the correct number of fixed-size buffers needed to hold
220 // a given number of seconds of PCM audio.
221 int num_buffers(scalar seconds) const
222 {
223 vorbis_info* info = ov_info(&file_, -1);
224 int count = scalar(info->rate) * // sample rate
225 scalar(info->channels) * // channels
226 scalar(NUM_SAMPLE_BITS) * // sample size
227 seconds * // time
228 SCALAR(0.125) / // bits to bytes
229 scalar(BUF_SIZE); // individual buffer size
230 return count;
231 }
232
233 static ALenum get_audio_format(const vorbis_info* info)
234 {
235 if (info->channels == 1)
236 return AL_FORMAT_MONO16;
237 else if (info->channels == 2)
238 return AL_FORMAT_STEREO16;
239
240 log_error("unsupported number of channels:", info->channels);
241 return 0;
242 }
243
244 private:
245
246 mutable OggVorbis_File file_;
247 mutable ALuint buffer_;
248 mutable uint64_t sample_;
249
250 sound_backend backend_;
251 };
252
253
254 class sound::impl
255 {
256 public:
257
258 impl()
259 {
260 init();
261 }
262
263 impl(const std::string& name)
264 {
265 init();
266 sample(name);
267 }
268
269 void init()
270 {
271 is_playing_ = false;
272 is_looping_ = false;
273
274 sample_ = 0;
275 buffer_size_ = SCALAR(1.0);
276
277 alGenSources(1, &source_);
278
279 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
280 alSourcef(source_, AL_PITCH, 1.0f);
281 alSourcef(source_, AL_GAIN, 1.0f);
282 alSourcefv(source_, AL_POSITION, zero);
283 alSourcefv(source_, AL_VELOCITY, zero);
284 }
285
286 ~impl()
287 {
288 stop();
289 deplete_stream();
290
291 alDeleteSources(1, &source_);
292 }
293
294
295 void deplete_stream()
296 {
297 if (is_streaming_)
298 {
299 ALint queued = 0;
300 alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
301 ALuint buffers[queued];
302 alSourceUnqueueBuffers(source_, queued, buffers);
303 alDeleteBuffers(queued, buffers);
304 }
305 else
306 {
307 alSourcei(source_, AL_BUFFER, AL_NONE);
308 }
309 }
310
311 void play()
312 {
313 if (queue_.empty() || is_playing_) return;
314
315 ALenum state;
316 alGetSourcei(source_, AL_SOURCE_STATE, &state);
317 switch (state)
318 {
319 case AL_INITIAL:
320 case AL_STOPPED:
321 if (is_streaming_)
322 {
323 start_update_timer();
324 num_buffers_ = queue_.front()->num_buffers(buffer_size_);
325 fill_stream();
326 is_playing_ = true;
327 alSourcei(source_, AL_LOOPING, false);
328 }
329 else
330 {
331 ALuint buffer = queue_.front()->read_all();
332 alSourcei(source_, AL_BUFFER, buffer);
333 alSourcei(source_, AL_LOOPING, is_looping_);
334 }
335 break;
336
337 case AL_PAUSED:
338 if (is_streaming_)
339 {
340 start_update_timer();
341 is_playing_ = true;
342 }
343 break;
344 }
345
346 alSourcePlay(source_);
347 }
348
349 void start_update_timer()
350 {
351 stream_timer_.init(boost::bind(&impl::stream_update,
352 this, _1, _2),
353 SCALAR(0.1), timer::repeat);
354
355 thread::main_runloop().add_timer(stream_timer_);
356
357 //thread thread;
358 //thread.runloop().add_timer(stream_timer_);
359 //if (!stream_thread_.is_valid())
360 //stream_thread_ = thread::detach(stream_timer_);
361 // FIXME: proper locking needs to be done; this is causing crashes
362 // on shutdown when sound resources are destroyed yet the stream
363 // thread is neither shutdown or notified.
364 }
365
366 void fill_stream()
367 {
368 ALenum type;
369 alGetSourcei(source_, AL_SOURCE_TYPE, &type);
370 ASSERT(type != AL_STATIC && "source shouldn't be static");
371
372 ALint processed = 0;
373 ALint queued = 0;
374 alGetSourcei(source_, AL_BUFFERS_QUEUED, &queued);
375 alGetSourcei(source_, AL_BUFFERS_PROCESSED, &processed);
376
377 int target_diff = num_buffers_ - queued + processed;
378
379 ALuint bufs[processed];
380 alSourceUnqueueBuffers(source_, processed, bufs);
381 for (int i = 0; i < processed; ++i)
382 {
383 ALuint& buf = bufs[i];
384
385 if (0 < target_diff)
386 {
387 if (queue_buffer(buf))
388 {
389 --target_diff;
390 }
391 else
392 {
393 alDeleteBuffers(processed - i - 1, &bufs[i+1]);
394 break;
395 }
396 }
397 else alDeleteBuffers(1, &buf);
398 }
399
400 if (stream_timer_.mode() != timer::invalid && 0 < target_diff)
401 {
402 for (int i = 0; i < target_diff; ++i)
403 {
404 if (!queue_buffer(AL_NONE)) break;
405 }
406 }
407
408 if (is_playing_)
409 {
410 ALenum state;
411 alGetSourcei(source_, AL_SOURCE_STATE, &state);
412 if (state != AL_PLAYING)
413 {
414 // restart playing if we're stopped but supposed to be
415 // playing... this means we didn't queue enough...
416 alSourcePlay(source_);
417 log_warning("not enough audio buffered");
418 }
419 }
420 }
421
422 // TODO: this function is ugly
423 bool queue_buffer(ALuint buffer)
424 {
425 if (buffer == AL_NONE) alGenBuffers(1, &buffer);
426
427 bool looped_once = false;
428
429 while (0 < queue_.size())
430 {
431 sound_handle sound = queue_.front();
432
433 bool result = sound->read(buffer, sample_);
434 if (result)
435 {
436 alSourceQueueBuffers(source_, 1, &buffer);
437 return true;
438 }
439 else
440 {
441 sample_ = 0;
442 queue_.pop_front();
443
444 if (0 < queue_.size())
445 {
446 sound_handle new_sound = queue_.front();
447 result = new_sound->read(buffer, sample_, sound);
448 if (result)
449 {
450 queue_.push_back(new_sound);
451 alSourceQueueBuffers(source_, 1, &buffer);
452 num_buffers_ = new_sound->num_buffers(buffer_size_);
453 return true;
454 }
455 else
456 {
457 queue_.pop_front();
458 queue_.push_front(sound);
459 }
460 }
461 else if (is_looping_ && !looped_once)
462 {
463 queue_.push_back(sound);
464 looped_once = true;
465 }
466 else
467 {
468 break;
469 }
470 }
471 }
472
473 alDeleteBuffers(1, &buffer);
474 is_playing_ = false;
475 stream_timer_.invalidate();
476 return false;
477 }
478
479 void stop()
480 {
481 alSourceStop(source_);
482 is_playing_ = false;
483
484 stream_timer_.invalidate();
485
486 sample_ = 0;
487 }
488
489 void pause()
490 {
491 alSourcePause(source_);
492 is_playing_ = false;
493
494 stream_timer_.invalidate();
495 }
496
497 void sample(const std::string& name)
498 {
499 stop();
500 deplete_stream();
501
502 queue_.clear();
503 queue(name);
504 is_streaming_ = false;
505 }
506
507 void queue(const std::string& name)
508 {
509 sound_handle handle = resource::load(name, "ogg");
510 if (handle) queue_.push_back(handle);
511 is_streaming_ = true;
512 }
513
514 bool is_playing() const
515 {
516 ALenum state;
517 alGetSourcei(source_, AL_SOURCE_STATE, &state);
518
519 if (state == AL_PLAYING) return true;
520 else return is_playing_;
521 }
522
523 void loop(bool looping)
524 {
525 is_looping_ = looping;
526
527 ALenum type;
528 alGetSourcei(source_, AL_SOURCE_TYPE, &type);
529 if (type != AL_STREAMING)
530 alSourcei(source_, AL_LOOPING, is_looping_);
531 }
532
533 void stream_update(timer& timer, scalar t)
534 {
535 log_debug("blaaaaaaaaaaaaaaaaaaaaaaaaaaahhhhhhhhhhhhhhhhrrrrrg");
536 fill_stream();
537 }
538
539 ALuint source_;
540
541 bool is_playing_;
542 bool is_looping_;
543 bool is_streaming_;
544
545 std::deque<sound_handle> queue_;
546 uint64_t sample_;
547 int num_buffers_;
548 scalar buffer_size_;
549
550 timer stream_timer_;
551
552 sound_backend backend_;
553 };
554
555
556 sound::sound() :
557 // pass through
558 impl_(new sound::impl) {}
559
560 sound::sound(const std::string& path) :
561 // pass through
562 impl_(new sound::impl(path)) {}
563
564 void sound::sample(const std::string& path)
565 {
566 // pass through
567 impl_->sample(path);
568 }
569
570 void sound::queue(const std::string& path)
571 {
572 // pass through
573 impl_->queue(path);
574 }
575
576 void sound::play()
577 {
578 // pass through
579 impl_->play();
580 }
581
582 void sound::stop()
583 {
584 // pass through
585 impl_->stop();
586 }
587
588 void sound::pause()
589 {
590 // pass through
591 impl_->pause();
592 }
593
594 void sound::toggle()
595 {
596 if (is_playing()) pause();
597 else play();
598 }
599
600 bool sound::is_playing() const
601 {
602 // pass through
603 return impl_->is_playing();
604 }
605
606 void sound::buffer_size(scalar seconds)
607 {
608 impl_->buffer_size_ = seconds;
609 }
610
611 void sound::position(const vector3& position)
612 {
613 float vec[3] = {position[0], position[1], position[2]};
614 alSourcefv(impl_->source_, AL_POSITION, vec);
615 }
616
617 void sound::velocity(const vector3& velocity)
618 {
619 float vec[3] = {velocity[0], velocity[1], velocity[2]};
620 alSourcefv(impl_->source_, AL_VELOCITY, vec);
621 }
622
623 void sound::gain(scalar gain)
624 {
625 alSourcef(impl_->source_, AL_GAIN, float(gain));
626 }
627
628 void sound::pitch(scalar pitch)
629 {
630 alSourcef(impl_->source_, AL_PITCH, float(pitch));
631 }
632
633 void sound::loop(bool looping)
634 {
635 // pass through
636 impl_->loop(looping);
637 }
638
639 void sound::listener_position(const vector3& position)
640 {
641 float vec[] = {position[0], position[1], position[2]};
642 alListenerfv(AL_POSITION, vec);
643 }
644
645 void sound::listener_velocity(const vector3& velocity)
646 {
647 float vec[] = {velocity[0], velocity[1], velocity[2]};
648 alListenerfv(AL_VELOCITY, vec);
649 }
650
651 void sound::listener_orientation(const vector3& forward,
652 const vector3& up)
653 {
654 float vec[6];
655 vec[0] = float(forward[0]);
656 vec[1] = float(forward[1]);
657 vec[2] = float(forward[2]);
658 vec[3] = float(up[0]);
659 vec[4] = float(up[1]);
660 vec[5] = float(up[2]);
661 alListenerfv(AL_ORIENTATION, vec);
662 }
663
664
665 } // namespace moof
666
This page took 0.056591 seconds and 4 git commands to generate.