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