1 /*
2 * updated for 4.9 inclusion by Ryan Dickie
3 * Originally done by KC/Milan
4 */
5
6 #include <stdio.h>
7 #include <string.h>
8
9 #include "allegro5/allegro.h"
10
11 #if defined(ALLEGRO_MACOSX) || defined(ALLEGRO_IPHONE)
12 #include <OpenAL/al.h>
13 #include <OpenAL/alc.h>
14 #else /* ALLEGRO_MACOSX */
15 #include <al.h>
16 #include <alc.h>
17 #endif /* ALLEGRO_MACOSX */
18
19 #include "allegro5/internal/aintern_audio.h"
20
21 ALLEGRO_DEBUG_CHANNEL("openal")
22
23 /* OpenAL vars */
24 static ALCdevice *openal_dev;
25 static ALCcontext *openal_context;
26
27 /* TODO: make these configurable */
28 static const size_t preferred_frag_size = 1024;
29 static const ALuint preferred_buf_count = 4;
30
openal_get_err_str(ALenum err)31 static const char *openal_get_err_str(ALenum err)
32 {
33 switch (err) {
34 case AL_NO_ERROR:
35 return "There is no OpenAL error";
36 case AL_INVALID_NAME:
37 return "A bad name (ID) was passed to OpenAL";
38 case AL_INVALID_ENUM:
39 return "An invalid enum was passed to OpenAL";
40 case AL_INVALID_VALUE:
41 return "An Invalid enum was passed to OpenAL";
42 case AL_INVALID_OPERATION:
43 return "The requestion operation is invalid";
44 case AL_OUT_OF_MEMORY:
45 return "OpenAL ran out of memory";
46 default:
47 return "Unknown error";
48 }
49 }
50
alc_get_err_str(ALCenum err)51 static const char *alc_get_err_str(ALCenum err)
52 {
53 switch (err) {
54 case ALC_NO_ERROR:
55 return "There is no OpenAL error";
56 case ALC_INVALID_DEVICE:
57 return "A bad device was passed to OpenAL";
58 case ALC_INVALID_CONTEXT:
59 return "An bad context was passed to OpenAL";
60 case ALC_INVALID_ENUM:
61 return "An Invalid enum was passed to OpenAL";
62 case ALC_INVALID_VALUE:
63 return "The requestion operation is invalid";
64 case ALC_OUT_OF_MEMORY:
65 return "OpenAL ran out of memory";
66 default:
67 return "Unknown error";
68 }
69 }
70
71 /* The open method starts up the driver and should lock the device, using the
72 previously set paramters, or defaults. It shouldn't need to start sending
73 audio data to the device yet, however. */
_openal_open(void)74 static int _openal_open(void)
75 {
76 ALenum openal_err;
77 ALCenum alc_err;
78
79 ALLEGRO_INFO("Starting OpenAL\n");
80
81 /* clear the error state */
82 openal_err = alGetError();
83
84 /* pick default device. always a good choice */
85 openal_dev = alcOpenDevice(NULL);
86
87 alc_err = ALC_NO_ERROR;
88 if (!openal_dev || (alc_err = alcGetError(openal_dev)) != ALC_NO_ERROR) {
89 ALLEGRO_ERROR("Could not open audio device: %s\n",
90 alc_get_err_str(alc_err));
91 return 1;
92 }
93
94 openal_context = alcCreateContext(openal_dev, NULL);
95 alc_err = ALC_NO_ERROR;
96 if (!openal_context || (alc_err = alcGetError(openal_dev)) != ALC_NO_ERROR) {
97 ALLEGRO_ERROR("Could not create current device context: %s\n",
98 alc_get_err_str(alc_err));
99 return 1;
100 }
101
102 alcMakeContextCurrent(openal_context);
103 #if !defined ALLEGRO_IPHONE
104 if ((alc_err = alcGetError(openal_dev)) != ALC_NO_ERROR) {
105 ALLEGRO_ERROR("Could not make context current: %s\n",
106 alc_get_err_str(alc_err));
107 return 1;
108 }
109
110 alDistanceModel(AL_NONE);
111 if ((openal_err = alGetError()) != AL_NO_ERROR) {
112 ALLEGRO_ERROR("Could not set distance model: %s\n",
113 openal_get_err_str(openal_err));
114 return 1;
115 }
116 #endif
117
118 ALLEGRO_DEBUG("Vendor: %s\n", alGetString(AL_VENDOR));
119 ALLEGRO_DEBUG("Version: %s\n", alGetString(AL_VERSION));
120 ALLEGRO_DEBUG("Renderer: %s\n", alGetString(AL_RENDERER));
121 ALLEGRO_DEBUG("Extensions: %s\n", alGetString(AL_EXTENSIONS));
122
123 return 0;
124 }
125
126 /* The close method should close the device, freeing any resources, and allow
127 other processes to use the device */
_openal_close(void)128 static void _openal_close(void)
129 {
130 /* clear error states */
131 alGetError();
132 alcGetError(openal_dev);
133
134 /* remove traces from openal */
135 alcMakeContextCurrent(NULL);
136 alcDestroyContext(openal_context);
137 alcCloseDevice(openal_dev);
138
139 /* reset the pointers to NULL */
140 openal_context = NULL;
141 openal_dev = NULL;
142 }
143
144 /* Custom struct to hold voice information OpenAL needs */
145 /* TODO: review */
146 typedef struct ALLEGRO_AL_DATA {
147 ALuint *buffers;
148
149 size_t num_buffers;
150 ALuint buffer_size;
151
152 ALuint source;
153 ALuint format;
154
155 ALLEGRO_THREAD *thread;
156 bool stopped;
157 } ALLEGRO_AL_DATA;
158
159 /* Custom routine which runs in another thread to periodically check if OpenAL
160 wants more data for a stream */
161 /* TODO: review */
_openal_update(ALLEGRO_THREAD * self,void * arg)162 static void *_openal_update(ALLEGRO_THREAD *self, void *arg)
163 {
164 ALLEGRO_VOICE *voice = (ALLEGRO_VOICE*) arg;
165 ALLEGRO_AL_DATA *ex_data = (ALLEGRO_AL_DATA*)voice->extra;
166 unsigned int i, samples_per_update;
167 unsigned int bytes_per_sample;
168 const void *data;
169 void *silence;
170
171 /* Streams should not be set to looping */
172 alSourcei(ex_data->source, AL_LOOPING, AL_FALSE);
173
174 silence = al_calloc(1, ex_data->buffer_size);
175 if (ex_data->format == AL_FORMAT_STEREO8 ||
176 ex_data->format == AL_FORMAT_MONO8) {
177 memset(silence, 0x80, ex_data->buffer_size);
178 }
179
180 for (i = 0; i < ex_data->num_buffers; i++) {
181 alBufferData(ex_data->buffers[i], ex_data->format, silence,
182 ex_data->buffer_size, voice->frequency);
183 }
184
185 alSourceQueueBuffers(ex_data->source, ex_data->num_buffers,
186 ex_data->buffers);
187
188 alSourcePlay(ex_data->source);
189
190 switch (ex_data->format) {
191 case AL_FORMAT_STEREO16:
192 bytes_per_sample = 4;
193 break;
194 case AL_FORMAT_STEREO8:
195 case AL_FORMAT_MONO16:
196 bytes_per_sample = 2;
197 break;
198 default:
199 bytes_per_sample = 1;
200 break;
201 }
202
203 samples_per_update = ex_data->buffer_size / bytes_per_sample;
204
205 data = silence;
206
207 while (!al_get_thread_should_stop(self)) {
208 ALint status = 0;
209
210 alGetSourcei(ex_data->source, AL_BUFFERS_PROCESSED, &status);
211 if (status <= 0) {
212 /* FIXME what is this for ? */
213 al_rest(0.001);
214 continue;
215 }
216
217 while (--status >= 0) {
218 ALuint buffer;
219
220 data = _al_voice_update(voice, voice->mutex, &samples_per_update);
221 if (data == NULL)
222 data = silence;
223
224 alSourceUnqueueBuffers(ex_data->source, 1, &buffer);
225 alBufferData(buffer, ex_data->format, data,
226 samples_per_update * bytes_per_sample, voice->frequency);
227 alSourceQueueBuffers(ex_data->source, 1, &buffer);
228 }
229 alGetSourcei(ex_data->source, AL_SOURCE_STATE, &status);
230 if (status == AL_STOPPED) {
231 alSourcePlay(ex_data->source);
232 }
233 }
234
235 alSourceStop(ex_data->source);
236
237 al_free(silence);
238
239 ex_data->stopped = true;
240 al_broadcast_cond(voice->cond);
241
242 return NULL;
243 }
244
245 /* The load_voice method loads a sample into the driver's memory. The voice's
246 'streaming' field will be set to false for these voices, and it's
247 'buffer_size' field will be the total length in bytes of the sample data.
248 The voice's attached sample's looping mode should be honored, and loading
249 must fail if it cannot be. */
_openal_load_voice(ALLEGRO_VOICE * voice,const void * data)250 static int _openal_load_voice(ALLEGRO_VOICE *voice, const void *data)
251 {
252 ALLEGRO_AL_DATA *ex_data = voice->extra;
253 ALenum openal_err;
254
255 if (voice->attached_stream->loop != ALLEGRO_PLAYMODE_ONCE &&
256 voice->attached_stream->loop != ALLEGRO_PLAYMODE_LOOP) {
257 return 1;
258 }
259
260 ex_data->buffer_size = voice->buffer_size;
261 if (!ex_data->buffer_size) {
262 ALLEGRO_ERROR("Voice buffer and data buffer size mismatch\n");
263 return 1;
264 }
265 ex_data->num_buffers = 1;
266
267 alGenSources(1, &ex_data->source);
268 if ((openal_err = alGetError()) != AL_NO_ERROR) {
269 ALLEGRO_ERROR("Could not generate (voice) source: %s\n",
270 openal_get_err_str(openal_err));
271 return 1;
272 }
273
274 ex_data->buffers = al_malloc(sizeof(ALuint) * ex_data->num_buffers);
275 if (!ex_data->buffers) {
276 alSourcei(ex_data->source, AL_BUFFER, 0);
277 alDeleteSources(1, &ex_data->source);
278 ALLEGRO_ERROR("Could not allocate voice buffer memory\n");
279 return 1;
280 }
281
282 alGenBuffers(ex_data->num_buffers, ex_data->buffers);
283 if ((openal_err = alGetError()) != AL_NO_ERROR) {
284 alSourcei(ex_data->source, AL_BUFFER, 0);
285 alDeleteSources(1, &ex_data->source);
286 al_free(ex_data->buffers);
287 ex_data->buffers = NULL;
288 ALLEGRO_ERROR("Could not generate (voice) buffer: %s\n",
289 openal_get_err_str(openal_err));
290 return 1;
291 }
292
293 /* copies data into a buffer */
294 alBufferData(ex_data->buffers[0], ex_data->format,
295 data, ex_data->buffer_size, voice->frequency);
296
297 /* sets the buffer */
298 alSourcei(ex_data->source, AL_BUFFER, ex_data->buffers[0]);
299
300 /* Loop / no loop? */
301 alSourcei(ex_data->source, AL_LOOPING,
302 (voice->attached_stream->loop != ALLEGRO_PLAYMODE_ONCE));
303
304 /* make sure the volume is on */
305 alSourcef(ex_data->source, AL_GAIN, 1.0f);
306
307 if ((openal_err = alGetError()) != AL_NO_ERROR) {
308 alSourcei(ex_data->source, AL_BUFFER, 0);
309 alDeleteSources(1, &ex_data->source);
310 alDeleteBuffers(ex_data->num_buffers, ex_data->buffers);
311 al_free(ex_data->buffers);
312 ex_data->buffers = NULL;
313 ALLEGRO_ERROR("Could not attach voice source: %s\n",
314 openal_get_err_str(openal_err));
315 return 1;
316 }
317
318 return 0;
319 }
320
321 /* The unload_voice method unloads a sample previously loaded with load_voice.
322 This method should not be called on a streaming voice. */
_openal_unload_voice(ALLEGRO_VOICE * voice)323 static void _openal_unload_voice(ALLEGRO_VOICE *voice)
324 {
325 ALLEGRO_AL_DATA *ex_data = voice->extra;
326
327 alSourcei(ex_data->source, AL_BUFFER, 0);
328 alDeleteSources(1, &ex_data->source);
329 alDeleteBuffers(ex_data->num_buffers, ex_data->buffers);
330 al_free(ex_data->buffers);
331 ex_data->buffers = NULL;
332 alGetError(); /* required! */
333 }
334
335
336 /* The start_voice should, surprise, start the voice. For streaming voices, it
337 should start polling the device and call _al_voice_update for audio data.
338 For non-streaming voices, it should resume playing from the last set
339 position */
_openal_start_voice(ALLEGRO_VOICE * voice)340 static int _openal_start_voice(ALLEGRO_VOICE *voice)
341 {
342 ALLEGRO_AL_DATA *ex_data = voice->extra;
343 ALenum openal_err;
344
345 /* playing a sample instead of a stream */
346 if (!voice->is_streaming) {
347 alSourcePlay(ex_data->source);
348 if ((openal_err = alGetError()) != AL_NO_ERROR) {
349 ALLEGRO_ERROR("Could not start voice: %s\n",
350 openal_get_err_str(openal_err));
351 return 1;
352 }
353
354 ALLEGRO_INFO("Starting voice\n");
355 return 0;
356 }
357
358 {
359 ex_data->buffer_size = voice->buffer_size;
360 if (!ex_data->buffer_size) {
361 switch (ex_data->format) {
362 case AL_FORMAT_STEREO16:
363 ex_data->buffer_size = preferred_frag_size * 4;
364 break;
365 case AL_FORMAT_STEREO8:
366 case AL_FORMAT_MONO16:
367 ex_data->buffer_size = preferred_frag_size * 2;
368 break;
369 default:
370 ex_data->buffer_size = preferred_frag_size;
371 break;
372 }
373 }
374
375 ex_data->num_buffers = voice->num_buffers;
376 if (!ex_data->num_buffers)
377 ex_data->num_buffers = preferred_buf_count;
378
379 alGenSources(1, &ex_data->source);
380 if (alGetError() != AL_NO_ERROR)
381 return 1;
382
383 ex_data->buffers = al_malloc(sizeof(ALuint) * ex_data->num_buffers);
384 if (!ex_data->buffers) {
385 alSourcei(ex_data->source, AL_BUFFER, 0);
386 alDeleteSources(1, &ex_data->source);
387 return 1;
388 }
389
390 alGenBuffers(ex_data->num_buffers, ex_data->buffers);
391 if (alGetError() != AL_NO_ERROR) {
392 alSourcei(ex_data->source, AL_BUFFER, 0);
393 alDeleteSources(1, &ex_data->source);
394 al_free(ex_data->buffers);
395 ex_data->buffers = NULL;
396 return 1;
397 }
398
399 alSourcef(ex_data->source, AL_GAIN, 1.0f);
400 if (alGetError() != AL_NO_ERROR) {
401 alSourcei(ex_data->source, AL_BUFFER, 0);
402 alDeleteSources(1, &ex_data->source);
403 alDeleteBuffers(ex_data->num_buffers, ex_data->buffers);
404 al_free(ex_data->buffers);
405 ex_data->buffers = NULL;
406 return 1;
407 }
408
409 ex_data->stopped = false;
410 ex_data->thread = al_create_thread(_openal_update, (void *)voice);
411 al_start_thread(ex_data->thread);
412 }
413
414 ALLEGRO_INFO("Starting voice\n");
415 return 0;
416 }
417
418 /* The stop_voice method should stop playback. For non-streaming voices, it
419 should leave the data loaded, and reset the voice position to 0. */
_openal_stop_voice(ALLEGRO_VOICE * voice)420 static int _openal_stop_voice(ALLEGRO_VOICE* voice)
421 {
422 ALLEGRO_AL_DATA *ex_data = voice->extra;
423 ALenum openal_err;
424
425 if (!ex_data->buffers) {
426 ALLEGRO_WARN("Trying to stop empty voice buffer\n");
427 return 1;
428 }
429
430 /* if playing a sample */
431 if (!voice->is_streaming) {
432 alSourceStop(ex_data->source);
433 if ((openal_err = alGetError()) != AL_NO_ERROR) {
434 ALLEGRO_ERROR("Could not stop voice: %s\n",
435 openal_get_err_str(openal_err));
436 return 1;
437 }
438 return 0;
439 }
440
441 if (ex_data->thread) {
442 al_set_thread_should_stop(ex_data->thread);
443 while (!ex_data->stopped) {
444 al_wait_cond(voice->cond, voice->mutex);
445 }
446 al_join_thread(ex_data->thread, NULL);
447 ex_data->thread = NULL;
448 ex_data->stopped = false;
449 }
450
451 alSourcei(ex_data->source, AL_BUFFER, 0);
452 alDeleteSources(1, &ex_data->source);
453 alDeleteBuffers(ex_data->num_buffers, ex_data->buffers);
454 al_free(ex_data->buffers);
455 ex_data->buffers = NULL;
456
457 alGetError(); /* required! */
458 return 0;
459 }
460
461 /* The voice_is_playing method should only be called on non-streaming sources,
462 and should return true if the voice is playing */
_openal_voice_is_playing(const ALLEGRO_VOICE * voice)463 static bool _openal_voice_is_playing(const ALLEGRO_VOICE *voice)
464 {
465 ALLEGRO_AL_DATA *ex_data = voice->extra;
466 ALint status;
467
468 if (!ex_data)
469 return false;
470
471 alGetSourcei(ex_data->source, AL_SOURCE_STATE, &status);
472 return (status == AL_PLAYING);
473 }
474
475 /* The allocate_voice method should grab a voice from the system, and allocate
476 any data common to streaming and non-streaming sources. */
_openal_allocate_voice(ALLEGRO_VOICE * voice)477 static int _openal_allocate_voice(ALLEGRO_VOICE *voice)
478 {
479 ALLEGRO_AL_DATA *ex_data;
480
481 /* OpenAL doesn't support very much! */
482 switch (voice->depth) {
483 case ALLEGRO_AUDIO_DEPTH_UINT8:
484 /* format supported */
485 break;
486 case ALLEGRO_AUDIO_DEPTH_INT8:
487 ALLEGRO_WARN("OpenAL requires 8-bit data to be unsigned\n");
488 return 1;
489 case ALLEGRO_AUDIO_DEPTH_UINT16:
490 ALLEGRO_WARN("OpenAL requires 16-bit data to be signed\n");
491 return 1;
492 case ALLEGRO_AUDIO_DEPTH_INT16:
493 /* format supported */
494 break;
495 case ALLEGRO_AUDIO_DEPTH_UINT24:
496 ALLEGRO_WARN("OpenAL does not support 24-bit data\n");
497 return 1;
498 case ALLEGRO_AUDIO_DEPTH_INT24:
499 ALLEGRO_WARN("OpenAL does not support 24-bit data\n");
500 return 1;
501 case ALLEGRO_AUDIO_DEPTH_FLOAT32:
502 ALLEGRO_WARN("OpenAL does not support 32-bit floating data\n");
503 return 1;
504 default:
505 ALLEGRO_WARN("Cannot allocate unknown voice depth\n");
506 return 1;
507 }
508
509 ex_data = al_calloc(1, sizeof(*ex_data));
510 if (!ex_data) {
511 ALLEGRO_ERROR("Could not allocate voice data memory\n");
512 return 1;
513 }
514
515 switch (voice->chan_conf) {
516 case ALLEGRO_CHANNEL_CONF_1:
517 /* format supported */
518 if (voice->depth == ALLEGRO_AUDIO_DEPTH_UINT8)
519 ex_data->format = AL_FORMAT_MONO8;
520 else
521 ex_data->format = AL_FORMAT_MONO16;
522 break;
523 case ALLEGRO_CHANNEL_CONF_2:
524 /* format supported */
525 if (voice->depth == ALLEGRO_AUDIO_DEPTH_UINT8)
526 ex_data->format = AL_FORMAT_STEREO8;
527 else
528 ex_data->format = AL_FORMAT_STEREO16;
529 break;
530 case ALLEGRO_CHANNEL_CONF_3:
531 ALLEGRO_ERROR("OpenAL does not support voice with 3 channel configuration\n");
532 al_free(ex_data);
533 return 1;
534 case ALLEGRO_CHANNEL_CONF_4:
535 ex_data->format = alGetEnumValue("AL_FORMAT_QUAD16");
536 if (ex_data->format) {
537 ALLEGRO_ERROR("OpenAL cannot allocate voice with 4.0 channel configuration\n");
538 al_free(ex_data);
539 return 1;
540 }
541 if (voice->depth == ALLEGRO_AUDIO_DEPTH_INT16) {
542 ALLEGRO_ERROR("OpenAL requires 16-bit signed data for 4 channel configuration\n");
543 al_free(ex_data);
544 return 1;
545 }
546 /* else it is supported */
547 break;
548 case ALLEGRO_CHANNEL_CONF_5_1:
549 ex_data->format = alGetEnumValue("AL_FORMAT_51CHN_16");
550 if (!ex_data->format) {
551 ALLEGRO_ERROR("Cannot allocate voice with 5.1 channel configuration\n");
552 al_free(ex_data);
553 return 1;
554 }
555 if (voice->depth == ALLEGRO_AUDIO_DEPTH_UINT16) {
556 ALLEGRO_ERROR("5.1 channel requires 16-bit signed data\n");
557 al_free(ex_data);
558 return 1;
559 }
560 /* else it is supported */
561 break;
562 case ALLEGRO_CHANNEL_CONF_6_1:
563 ex_data->format = alGetEnumValue("AL_FORMAT_61CHN_16");
564 if (!ex_data->format) {
565 ALLEGRO_ERROR("Cannot allocate voice with 6.1 channel configuration\n");
566 al_free(ex_data);
567 return 1;
568 }
569 if (voice->depth == ALLEGRO_AUDIO_DEPTH_UINT16) {
570 ALLEGRO_ERROR("6.1 channel requires 16-bit signed data\n");
571 al_free(ex_data);
572 return 1;
573 }
574 /* else it is supported */
575 break;
576 case ALLEGRO_CHANNEL_CONF_7_1:
577 ex_data->format = alGetEnumValue("AL_FORMAT_71CHN_16");
578 if (!ex_data->format) {
579 ALLEGRO_ERROR("Cannot allocate voice with 7.1 channel configuration\n");
580 al_free(ex_data);
581 return 1;
582 }
583 if (voice->depth == ALLEGRO_AUDIO_DEPTH_UINT16) {
584 ALLEGRO_ERROR("7.1 channel requires 16-bit signed data\n");
585 al_free(ex_data);
586 return 1;
587 }
588 /* else it is supported */
589 break;
590 default:
591 ALLEGRO_ERROR("Cannot allocate voice with unknown channel configuration\n");
592 al_free(ex_data);
593 return 1;
594 }
595
596 voice->extra = ex_data;
597 ex_data->thread = NULL;
598 ex_data->stopped = false;
599
600 return 0;
601 }
602
603 /* The deallocate_voice method should free the resources for the given voice,
604 but still retain a hold on the device. The voice should be stopped and
605 unloaded by the time this is called */
_openal_deallocate_voice(ALLEGRO_VOICE * voice)606 static void _openal_deallocate_voice(ALLEGRO_VOICE *voice)
607 {
608 ALLEGRO_AL_DATA *ex_data = voice->extra;
609 ASSERT(ex_data->thread == NULL);
610 (void)ex_data;
611
612 al_free(voice->extra);
613 voice->extra = NULL;
614 }
615
616 /* The get_voice_position method should return the current sample position of
617 the voice (sample_pos = byte_pos / (depth/8) / channels). This should never
618 be called on a streaming voice. */
_openal_get_voice_position(const ALLEGRO_VOICE * voice)619 static unsigned int _openal_get_voice_position(const ALLEGRO_VOICE *voice)
620 {
621 ALLEGRO_AL_DATA *ex_data = voice->extra;
622 ALint pos;
623
624 alGetSourcei(ex_data->source, AL_SAMPLE_OFFSET, &pos);
625 if (alGetError() != AL_NO_ERROR)
626 return 0;
627 return pos;
628 }
629
630 /* The set_voice_position method should set the voice's playback position,
631 given the value in samples. This should never be called on a streaming
632 voice. */
_openal_set_voice_position(ALLEGRO_VOICE * voice,unsigned int val)633 static int _openal_set_voice_position(ALLEGRO_VOICE *voice, unsigned int val)
634 {
635 ALLEGRO_AL_DATA *ex_data = voice->extra;
636
637 alSourcei(ex_data->source, AL_SAMPLE_OFFSET, val);
638 if (alGetError() != AL_NO_ERROR)
639 return 1;
640 return 0;
641 }
642
643 ALLEGRO_AUDIO_DRIVER _al_kcm_openal_driver = {
644 "OpenAL",
645
646 _openal_open,
647 _openal_close,
648
649 _openal_allocate_voice,
650 _openal_deallocate_voice,
651
652 _openal_load_voice,
653 _openal_unload_voice,
654
655 _openal_start_voice,
656 _openal_stop_voice,
657
658 _openal_voice_is_playing,
659
660 _openal_get_voice_position,
661 _openal_set_voice_position,
662
663 NULL,
664 NULL
665 };
666
667 /* vim: set sts=3 sw=3 et: */
668