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