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