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