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