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