]> Dogcows Code - chaz/yoink/blob - src/moof/sound.cc
a1da62b8be10c9d1c2567f2931e90a1eb9841071
[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 <AL/al.h>
20 #include <AL/alc.h>
21 #include <vorbis/codec.h>
22 #include <vorbis/vorbisfile.h>
23
24 #include "log.hh"
25 #include "manager.hh"
26 #include "sound.hh"
27 #include "timer.hh"
28
29 #define BUF_SIZE (64 * 1024)
30 //#define BUF_SIZE (5*2048)
31
32 namespace moof {
33
34
35 class impl
36 {
37 public:
38
39 impl()
40 {
41 resource::register_type<sound_stream>("ogg");
42 }
43
44 ~impl()
45 {
46 resource::unregister_type("ogg");
47 }
48 };
49 static impl impl;
50
51
52 class sound::impl
53 {
54 public:
55
56 static ALenum get_audio_format(const vorbis_info* audioInfo)
57 {
58 if (audioInfo->channels == 1) return AL_FORMAT_MONO16;
59 else return AL_FORMAT_STEREO16;
60 }
61
62
63 class buffer;
64 typedef boost::shared_ptr<buffer> buffer_ptr;
65
66 class buffer : public manager<buffer>
67 {
68 public:
69
70 buffer() :
71 buffer_(-1)
72 {
73 mOggStream.datasource = 0;
74 }
75
76 ~buffer()
77 {
78 if (mOggStream.datasource)
79 {
80 ov_clear(&mOggStream);
81 }
82 if (int(buffer_) != -1) alDeleteBuffers(1, &buffer_);
83 }
84
85
86 void init(const std::string& path)
87 {
88 log_info("initializing audio buffer...");
89 if (mOggStream.datasource)
90 {
91 ov_clear(&mOggStream);
92 mOggStream.datasource = 0;
93 }
94
95 if (ov_fopen((char*)path.c_str(), &mOggStream) < 0)
96 {
97 throw std::runtime_error("problem reading audio: " + path);
98 }
99
100 vorbis_info* vorbisInfo = ov_info(&mOggStream, -1);
101 mFormat = get_audio_format(vorbisInfo);
102 mFreq = vorbisInfo->rate;
103 }
104
105
106 void load_all(ALuint source)
107 {
108 if (!mOggStream.datasource) init(name());
109 if (!mOggStream.datasource) return;
110
111 char data[BUF_SIZE];
112 int size = 0;
113
114 for (;;)
115 {
116 int section;
117 int result = ov_read(&mOggStream, data + size,
118 BUF_SIZE - size, 0, 2, 1, &section);
119
120 if (result > 0)
121 {
122 size += result;
123 }
124 else
125 {
126 if (result < 0) log_warning("vorbis playback error");
127 break;
128 }
129 }
130 if (size == 0)
131 {
132 log_warning("decoded no bytes from", name());
133 return;
134 }
135
136 alGenBuffers(1, &buffer_);
137
138 alBufferData(buffer_, mFormat, data, size, mFreq);
139 alSourcei(source, AL_BUFFER, buffer_);
140
141 // don't need to keep this loaded
142 ov_clear(&mOggStream);
143 mOggStream.datasource = 0;
144 }
145
146 bool stream(ALuint buffer)
147 {
148 char data[BUF_SIZE];
149 int size = 0;
150
151 while (size < BUF_SIZE)
152 {
153 int section;
154 int result = ov_read(&mOggStream, data + size,
155 BUF_SIZE - size, 0, 2, 1, &section);
156
157 if (result > 0)
158 {
159 size += result;
160 }
161 else
162 {
163 if (result < 0) log_warning("vorbis playback error");
164 break;
165 }
166 }
167
168 if (size == 0) return false;
169
170 alBufferData(buffer, mFormat, data, size, mFreq);
171
172 return true;
173 }
174
175 void rewind()
176 {
177 if (!mOggStream.datasource) init(name());
178 else ov_raw_seek(&mOggStream, 0);
179 }
180
181
182 private:
183
184 OggVorbis_File mOggStream;
185 ALenum mFormat;
186 ALsizei mFreq;
187 ALuint buffer_;
188 };
189
190
191 impl()
192 {
193 init();
194 }
195
196 impl(const std::string& path)
197 {
198 log_info("sound::impl constructor");
199 init();
200 enqueue(path);
201 }
202
203 void init()
204 {
205 retain_backend();
206
207 is_loaded_ = false;
208 is_playing_ = false;
209 is_looping_ = false;
210
211 alGenSources(1, &source_);
212
213 ALfloat zero[] = {0.0f, 0.0f, 0.0f};
214 alSourcef(source_, AL_PITCH, 1.0f);
215 alSourcef(source_, AL_GAIN, 1.0f);
216 alSourcefv(source_, AL_POSITION, zero);
217 alSourcefv(source_, AL_VELOCITY, zero);
218
219 alSourcei(source_, AL_LOOPING, is_looping_);
220 }
221
222 ~impl()
223 {
224 stop();
225
226 alDeleteSources(1, &source_);
227
228 while (!buffers_.empty())
229 {
230 alDeleteBuffers(1, &buffers_.back());
231 buffers_.pop_back();
232 }
233
234 release_backend();
235 }
236
237
238 void play()
239 {
240 if (queue_.empty()) return;
241
242 if (!is_loaded_) queue_.front()->load_all(source_);
243
244 alSourcePlay(source_);
245 is_loaded_ = true;
246 }
247
248
249 void play_stream()
250 {
251 if (queue_.empty()) return;
252
253 if (!is_playing_)
254 {
255 alSourcei(source_, AL_LOOPING, false);
256 buffer_stream();
257 }
258
259 if (!stream_timer_.is_valid())
260 {
261 stream_timer_.init(boost::bind(&impl::stream_update, this, _1, _2),
262 1.0, timer::repeat);
263 }
264
265 alSourcePlay(source_);
266 is_playing_ = true;
267 }
268
269 void buffer_stream()
270 {
271 ALuint buffer;
272 for (int i = buffers_.size(); i <= 8; ++i)
273 {
274 alGenBuffers(1, &buffer);
275
276 if (queue_.front()->stream(buffer))
277 {
278 alSourceQueueBuffers(source_, 1, &buffer);
279 buffers_.push_back(buffer);
280 }
281 else
282 {
283 alDeleteBuffers(1, &buffer);
284 break;
285 }
286 }
287 }
288
289
290 void update()
291 {
292 ALint finished = 0;
293
294 alGetSourcei(source_, AL_BUFFERS_PROCESSED, &finished);
295
296 while (finished-- > 0)
297 {
298 ALuint bufferObj;
299 alSourceUnqueueBuffers(source_, 1, &bufferObj);
300
301 buffer_ptr buffer = queue_.front();
302 bool streamed = buffer->stream(bufferObj);
303
304 if (streamed)
305 {
306 alSourceQueueBuffers(source_, 1, &bufferObj);
307 }
308 else
309 {
310 // the buffer couldn't be streamed, so get rid of it
311 queue_.pop_front();
312
313 if (!queue_.empty())
314 {
315 // begin the next buffer in the queue
316 queue_.front()->rewind();
317 queue_.front()->stream(bufferObj);
318 alSourceQueueBuffers(source_, 1, &bufferObj);
319 log_info("loading new buffer");
320
321 // queue up any unused buffers
322 buffer_stream();
323 }
324 else if (is_looping_)
325 {
326 // reload the same buffer
327 queue_.push_back(buffer);
328 buffer->rewind();
329 buffer->stream(bufferObj);
330 alSourceQueueBuffers(source_, 1, &bufferObj);
331 log_info("looping same buffer");
332 }
333 else
334 {
335 // nothing more to play, stopping...
336 is_playing_ = false;
337 std::remove(buffers_.begin(), buffers_.end(),
338 bufferObj);
339 }
340 }
341 }
342
343 ALenum state;
344 alGetSourcei(source_, AL_SOURCE_STATE, &state);
345
346 // restart playing if we're stopped but supposed to be playing...
347 // this means we didn't queue enough and the audio skipped :-(
348 if (is_playing_ && state != AL_PLAYING)
349 {
350 alSourcePlay(source_);
351 }
352 }
353
354
355 void stop()
356 {
357 alSourceStop(source_);
358 is_playing_ = false;
359
360 stream_timer_.invalidate();
361 }
362
363 void pause()
364 {
365 alSourcePause(source_);
366 is_playing_ = false;
367
368 stream_timer_.invalidate();
369 }
370
371
372 void sample(const std::string& path)
373 {
374 stop();
375 alSourcei(source_, AL_BUFFER, AL_NONE);
376
377 queue_.clear();
378 is_loaded_ = false;
379
380 enqueue(path);
381
382 while (!buffers_.empty())
383 {
384 alDeleteBuffers(1, &buffers_.back());
385 buffers_.pop_back();
386 }
387 }
388
389 void enqueue(const std::string& path)
390 {
391 buffer_ptr buffer = buffer::instance(path);
392 queue_.push_back(buffer);
393 }
394
395
396 bool is_playing() const
397 {
398 if (is_playing_) return true;
399
400 ALenum state;
401 alGetSourcei(source_, AL_SOURCE_STATE, &state);
402
403 return state == AL_PLAYING;
404 }
405
406
407 void loop(bool looping)
408 {
409 is_looping_ = looping;
410
411 ALenum type;
412 alGetSourcei(source_, AL_SOURCE_TYPE, &type);
413
414 if (type != AL_STREAMING)
415 {
416 alSourcei(source_, AL_LOOPING, is_looping_);
417 }
418 }
419
420
421 void stream_update(timer& timer, scalar t)
422 {
423 update();
424 // TODO: might be nice to also allow using threads for streaming
425 // rather than a timer, probably as a compile-time option
426 }
427
428
429 static void retain_backend()
430 {
431 if (retain_count_++ == 0)
432 {
433 al_device_ = alcOpenDevice(0);
434 al_context_ = alcCreateContext(al_device_, 0);
435 if (!al_device_ || !al_context_)
436 {
437 const char* error = alcGetString(al_device_,
438 alcGetError(al_device_));
439 log_error("audio subsystem initialization failure", error);
440 }
441 else
442 {
443 alcMakeContextCurrent(al_context_);
444 log_info << "opened sound device `"
445 << alcGetString(al_device_,
446 ALC_DEFAULT_DEVICE_SPECIFIER)
447 << "'" << std::endl;
448 }
449 }
450 }
451
452 static void release_backend()
453 {
454 if (--retain_count_ == 0)
455 {
456 alcMakeContextCurrent(0);
457 alcDestroyContext(al_context_);
458 alcCloseDevice(al_device_);
459 }
460 }
461
462
463 ALuint source_;
464 std::list<ALuint> buffers_;
465
466 bool is_loaded_;
467 bool is_playing_;
468 bool is_looping_;
469
470 std::deque<buffer_ptr> queue_;
471
472 timer stream_timer_;
473
474 static unsigned retain_count_;
475 static ALCdevice* al_device_;
476 static ALCcontext* al_context_;
477 };
478
479 unsigned sound::impl::retain_count_ = 0;
480 ALCdevice* sound::impl::al_device_ = 0;
481 ALCcontext* sound::impl::al_context_ = 0;
482
483
484 //sound::sound() :
485 //// pass through
486 //impl_(new sound::impl) {}
487
488 sound::sound(const std::string& path) :
489 // pass through
490 impl_(new sound::impl(path))
491 {
492 log_info("sound constructor");
493 }
494
495
496 void sound::sample(const std::string& path)
497 {
498 // pass through
499 impl_->sample(path);
500 }
501
502
503 void sound::play()
504 {
505 // pass through
506 impl_->play();
507 }
508
509 void sound::stop()
510 {
511 // pass through
512 impl_->stop();
513 }
514
515 void sound::pause()
516 {
517 // pass through
518 impl_->pause();
519 }
520
521
522 void sound::toggle()
523 {
524 if (is_playing()) pause();
525 else play();
526 }
527
528 bool sound::is_playing() const
529 {
530 // pass through
531 return impl_->is_playing();
532 }
533
534
535 void sound::position(const vector3& position)
536 {
537 float vec[3] = {position[0], position[1], position[2]};
538 alSourcefv(impl_->source_, AL_POSITION, vec);
539 }
540
541 void sound::velocity(const vector3& velocity)
542 {
543 float vec[3] = {velocity[0], velocity[1], velocity[2]};
544 alSourcefv(impl_->source_, AL_VELOCITY, vec);
545 }
546
547 void sound::gain(scalar gain)
548 {
549 alSourcef(impl_->source_, AL_GAIN, float(gain));
550 }
551
552 void sound::pitch(scalar pitch)
553 {
554 alSourcef(impl_->source_, AL_PITCH, float(pitch));
555 }
556
557 void sound::loop(bool looping)
558 {
559 // pass through
560 impl_->loop(looping);
561 }
562
563
564 void sound::listener_position(const vector3& position)
565 {
566 float vec[] = {position[0], position[1], position[2]};
567 alListenerfv(AL_POSITION, vec);
568 }
569
570 void sound::listener_velocity(const vector3& velocity)
571 {
572 float vec[] = {velocity[0], velocity[1], velocity[2]};
573 alListenerfv(AL_VELOCITY, vec);
574 }
575
576 void sound::listener_orientation(const vector3& forward,
577 const vector3& up)
578 {
579 float vec[6];
580 vec[0] = float(forward[0]);
581 vec[1] = float(forward[1]);
582 vec[2] = float(forward[2]);
583 vec[3] = float(up[0]);
584 vec[4] = float(up[1]);
585 vec[5] = float(up[2]);
586 alListenerfv(AL_ORIENTATION, vec);
587 }
588
589
590 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
591
592
593 void sound_stream::enqueue(const std::string& path)
594 {
595 // pass through
596 impl_->enqueue(path);
597 }
598
599
600 void sound_stream::play()
601 {
602 // pass through
603 impl_->play_stream();
604 }
605
606
607 } // namespace moof
608
This page took 0.055146 seconds and 3 git commands to generate.