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