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