]> git.jsancho.org Git - lugaru.git/blob - Source/openal_wrapper.cpp
79be832f787dec547c6e23058216dca763e0e21e
[lugaru.git] / Source / openal_wrapper.cpp
1 /*
2 Copyright (C) 2003, 2010 - Wolfire Games
3
4 This file is part of Lugaru.
5
6 Lugaru is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation; either version 2
9 of the License, or (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15 See the GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
20 */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include "Quaternions.h"
27 #include "openal_wrapper.h"
28 #include "Sounds.h"
29
30 // NOTE:
31 // FMOD uses a Left Handed Coordinate system, OpenAL uses a Right Handed
32 //  one...so we just need to flip the sign on the Z axis when appropriate.
33
34 #define DYNAMIC_LOAD_OPENAL 0
35
36 #if DYNAMIC_LOAD_OPENAL
37
38 #include <dlfcn.h>
39
40 #define AL_FUNC(t,ret,fn,params,call,rt) \
41     extern "C" { \
42         static ret ALAPIENTRY (*p##fn) params = NULL; \
43         ret ALAPIENTRY fn params { rt p##fn call; } \
44     }
45 #include "alstubs.h"
46 #undef AL_FUNC
47
48 static void *aldlhandle = NULL;
49
50 static bool lookup_alsym(const char *funcname, void **func, const char *libname)
51 {
52     if (!aldlhandle)
53         return false;
54
55     *func = dlsym(aldlhandle, funcname);
56     if (*func == NULL) {
57         fprintf(stderr, "Failed to find OpenAL symbol \"%s\" in \"%s\"\n",
58                 funcname, libname);
59         return false;
60     }
61     return true;
62 }
63
64 static void unload_alsyms(void)
65 {
66 #define AL_FUNC(t,ret,fn,params,call,rt) p##fn = NULL;
67 #include "alstubs.h"
68 #undef AL_FUNC
69     if (aldlhandle) {
70         dlclose(aldlhandle);
71         aldlhandle = NULL;
72     }
73 }
74
75 static bool lookup_all_alsyms(const char *libname)
76 {
77     if (!aldlhandle) {
78         if ( (aldlhandle = dlopen(libname, RTLD_GLOBAL | RTLD_NOW)) == NULL )
79             return false;
80     }
81
82     bool retval = true;
83 #define AL_FUNC(t,ret,fn,params,call,rt) \
84         if (!lookup_alsym(#fn, (void **) &p##fn, libname)) retval = false;
85 #include "alstubs.h"
86 #undef AL_FUNC
87
88     if (!retval)
89         unload_alsyms();
90
91     return retval;
92 }
93 #else
94 #define lookup_all_alsyms(x) (true)
95 #define unload_alsyms()
96 #endif
97
98 typedef struct {
99     ALuint sid;
100     OPENAL_SAMPLE *sample;
101     bool startpaused;
102     float position[3];
103 } OPENAL_Channels;
104
105 typedef struct OPENAL_SAMPLE {
106     char *name;
107     ALuint bid;  // buffer id.
108     int mode;
109     int is2d;
110 } OPENAL_SAMPLE;
111
112 static size_t num_channels = 0;
113 static OPENAL_Channels *impl_channels = NULL;
114 static bool initialized = false;
115 static float listener_position[3];
116
117 static void set_channel_position(const int channel, const float x,
118                                  const float y, const float z)
119 {
120     OPENAL_Channels *chan = &impl_channels[channel];
121
122     chan->position[0] = x;
123     chan->position[1] = y;
124     chan->position[2] = z;
125
126     OPENAL_SAMPLE *sptr = chan->sample;
127     if (sptr == NULL)
128         return;
129
130     const ALuint sid = chan->sid;
131     const bool no_attenuate = sptr->is2d;
132
133     if (no_attenuate) {
134         alSourcei(sid, AL_SOURCE_RELATIVE, AL_TRUE);
135         alSource3f(sid, AL_POSITION, 0.0f, 0.0f, 0.0f);
136     } else {
137         alSourcei(sid, AL_SOURCE_RELATIVE, AL_FALSE);
138         alSource3f(sid, AL_POSITION, x, y, z);
139     }
140 }
141
142
143 AL_API void OPENAL_3D_Listener_SetAttributes(const float *pos, const float *vel, float fx, float fy, float fz, float tx, float ty, float tz)
144 {
145     if (!initialized)
146         return;
147     if (pos != NULL) {
148         alListener3f(AL_POSITION, pos[0], pos[1], -pos[2]);
149         listener_position[0] = pos[0];
150         listener_position[1] = pos[1];
151         listener_position[2] = -pos[2];
152     }
153
154     ALfloat vec[6] = { fx, fy, -fz, tz, ty, -tz };
155     alListenerfv(AL_ORIENTATION, vec);
156
157     // we ignore velocity, since doppler's broken in the Linux AL at the moment...
158
159     // adjust existing positions...
160     for (int i = 0; i < num_channels; i++) {
161         const float *p = impl_channels[i].position;
162         set_channel_position(i, p[0], p[1], p[2]);
163     }
164 }
165
166 AL_API signed char OPENAL_3D_SetAttributes(int channel, const float *pos, const float *vel)
167 {
168     if (!initialized)
169         return false;
170     if ((channel < 0) || (channel >= num_channels))
171         return false;
172
173     if (pos != NULL)
174         set_channel_position(channel, pos[0], pos[1], -pos[2]);
175
176     // we ignore velocity, since doppler's broken in the Linux AL at the moment...
177
178     return true;
179 }
180
181 AL_API signed char OPENAL_3D_SetAttributes_(int channel, const XYZ &pos, const float *vel)
182 {
183     if (!initialized)
184         return false;
185     if ((channel < 0) || (channel >= num_channels))
186         return false;
187
188     set_channel_position(channel, pos.x, pos.y, -pos.z);
189
190     return true;
191 }
192
193 AL_API signed char OPENAL_Init(int mixrate, int maxsoftwarechannels, unsigned int flags)
194 {
195     if (initialized)
196         return false;
197     if (maxsoftwarechannels == 0)
198         return false;
199
200     if (flags != 0)  // unsupported.
201         return false;
202
203     if (!lookup_all_alsyms("./openal.so")) { // !!! FIXME: linux specific lib name
204         if (!lookup_all_alsyms("openal.so.1")) { // !!! FIXME: linux specific lib name
205             if (!lookup_all_alsyms("openal.so"))  // !!! FIXME: linux specific lib name
206                 return false;
207         }
208     }
209
210     ALCdevice *dev = alcOpenDevice(NULL);
211     if (!dev)
212         return false;
213
214     ALint caps[] = { ALC_FREQUENCY, mixrate, 0 };
215     ALCcontext *ctx = alcCreateContext(dev, caps);
216     if (!ctx) {
217         alcCloseDevice(dev);
218         return false;
219     }
220
221     alcMakeContextCurrent(ctx);
222     alcProcessContext(ctx);
223
224     bool cmdline(const char * cmd);
225     if (cmdline("openalinfo")) {
226         printf("AL_VENDOR: %s\n", (char *) alGetString(AL_VENDOR));
227         printf("AL_RENDERER: %s\n", (char *) alGetString(AL_RENDERER));
228         printf("AL_VERSION: %s\n", (char *) alGetString(AL_VERSION));
229         printf("AL_EXTENSIONS: %s\n", (char *) alGetString(AL_EXTENSIONS));
230     }
231
232     num_channels = maxsoftwarechannels;
233     impl_channels = new OPENAL_Channels[maxsoftwarechannels];
234     memset(impl_channels, '\0', sizeof (OPENAL_Channels) * num_channels);
235     for (int i = 0; i < num_channels; i++)
236         alGenSources(1, &impl_channels[i].sid);  // !!! FIXME: verify this didn't fail!
237
238     initialized = true;
239     return true;
240 }
241
242 AL_API void OPENAL_Close()
243 {
244     if (!initialized)
245         return;
246
247     ALCcontext *ctx = alcGetCurrentContext();
248     if (ctx) {
249         for (int i = 0; i < num_channels; i++) {
250             alSourceStop(impl_channels[i].sid);
251             alSourcei(impl_channels[i].sid, AL_BUFFER, 0);
252             alDeleteSources(1, &impl_channels[i].sid);
253         }
254         ALCdevice *dev = alcGetContextsDevice(ctx);
255         alcMakeContextCurrent(NULL);
256         alcSuspendContext(ctx);
257         alcDestroyContext(ctx);
258         alcCloseDevice(dev);
259     }
260
261     num_channels = 0;
262     delete[] impl_channels;
263     impl_channels = NULL;
264
265     unload_alsyms();
266     initialized = false;
267 }
268
269 static OPENAL_SAMPLE *OPENAL_GetCurrentSample(int channel)
270 {
271     if (!initialized)
272         return NULL;
273     if ((channel < 0) || (channel >= num_channels))
274         return NULL;
275     return impl_channels[channel].sample;
276 }
277
278 static signed char OPENAL_GetPaused(int channel)
279 {
280     if (!initialized)
281         return false;
282     if ((channel < 0) || (channel >= num_channels))
283         return false;
284     if (impl_channels[channel].startpaused)
285         return(true);
286
287     ALint state = 0;
288     alGetSourceiv(impl_channels[channel].sid, AL_SOURCE_STATE, &state);
289     return((state == AL_PAUSED) ? true : false);
290 }
291
292 static unsigned int OPENAL_GetLoopMode(int channel)
293 {
294     if (!initialized)
295         return 0;
296     if ((channel < 0) || (channel >= num_channels))
297         return 0;
298     ALint loop = 0;
299     alGetSourceiv(impl_channels[channel].sid, AL_LOOPING, &loop);
300     if (loop)
301         return(OPENAL_LOOP_NORMAL);
302     return OPENAL_LOOP_OFF;
303 }
304
305 static signed char OPENAL_IsPlaying(int channel)
306 {
307     if (!initialized)
308         return false;
309     if ((channel < 0) || (channel >= num_channels))
310         return false;
311     ALint state = 0;
312     alGetSourceiv(impl_channels[channel].sid, AL_SOURCE_STATE, &state);
313     return((state == AL_PLAYING) ? true : false);
314 }
315
316 static int OPENAL_PlaySoundEx(int channel, OPENAL_SAMPLE *sptr, OPENAL_DSPUNIT *dsp, signed char startpaused)
317 {
318     if (!initialized)
319         return -1;
320     if (sptr == NULL)
321         return -1;
322     if (dsp != NULL)
323         return -1;
324     if (channel == OPENAL_FREE) {
325         for (int i = 0; i < num_channels; i++) {
326             ALint state = 0;
327             alGetSourceiv(impl_channels[i].sid, AL_SOURCE_STATE, &state);
328             if ((state != AL_PLAYING) && (state != AL_PAUSED)) {
329                 channel = i;
330                 break;
331             }
332         }
333     }
334
335     if ((channel < 0) || (channel >= num_channels))
336         return -1;
337     alSourceStop(impl_channels[channel].sid);
338     impl_channels[channel].sample = sptr;
339     alSourcei(impl_channels[channel].sid, AL_BUFFER, sptr->bid);
340     alSourcei(impl_channels[channel].sid, AL_LOOPING, (sptr->mode == OPENAL_LOOP_OFF) ? AL_FALSE : AL_TRUE);
341     set_channel_position(channel, 0.0f, 0.0f, 0.0f);
342
343     impl_channels[channel].startpaused = ((startpaused) ? true : false);
344     if (!startpaused)
345         alSourcePlay(impl_channels[channel].sid);
346     return channel;
347 }
348
349
350 static void *decode_to_pcm(const char *_fname, ALenum &format, ALsizei &size, ALuint &freq)
351 {
352 #ifdef __POWERPC__
353     const int bigendian = 1;
354 #else
355     const int bigendian = 0;
356 #endif
357
358     // !!! FIXME: if it's not Ogg, we don't have a decoder. I'm lazy.  :/
359     char *fname = (char *) alloca(strlen(_fname) + 16);
360     strcpy(fname, _fname);
361     char *ptr = strchr(fname, '.');
362     if (ptr)
363         *ptr = '\0';
364     strcat(fname, ".ogg");
365
366     // just in case...
367 #undef fopen
368     FILE *io = fopen(fname, "rb");
369     if (io == NULL)
370         return NULL;
371
372     ALubyte *retval = NULL;
373
374 #if 0  // untested, so disable this!
375     // Can we just feed it to the AL compressed?
376     if (alIsExtensionPresent((const ALubyte *) "AL_EXT_vorbis")) {
377         format = alGetEnumValue((const ALubyte *) "AL_FORMAT_VORBIS_EXT");
378         freq = 44100;
379         fseek(io, 0, SEEK_END);
380         size = ftell(io);
381         fseek(io, 0, SEEK_SET);
382         retval = (ALubyte *) malloc(size);
383         size_t rc = fread(retval, size, 1, io);
384         fclose(io);
385         if (rc != 1) {
386             free(retval);
387             return NULL;
388         }
389         return retval;
390     }
391 #endif
392
393     // Uncompress and feed to the AL.
394     OggVorbis_File vf;
395     memset(&vf, '\0', sizeof (vf));
396     if (ov_open(io, &vf, NULL, 0) == 0) {
397         int bitstream = 0;
398         vorbis_info *info = ov_info(&vf, -1);
399         size = 0;
400         format = (info->channels == 1) ? AL_FORMAT_MONO16 : AL_FORMAT_STEREO16;
401         freq = info->rate;
402
403         if ((info->channels != 1) && (info->channels != 2)) {
404             ov_clear(&vf);
405             return NULL;
406         }
407
408         char buf[1024 * 16];
409         long rc = 0;
410         size_t allocated = 64 * 1024;
411         retval = (ALubyte *) malloc(allocated);
412         while ( (rc = ov_read(&vf, buf, sizeof (buf), bigendian, 2, 1, &bitstream)) != 0 ) {
413             if (rc > 0) {
414                 size += rc;
415                 if (size >= allocated) {
416                     allocated *= 2;
417                     ALubyte *tmp = (ALubyte *) realloc(retval, allocated);
418                     if (tmp == NULL) {
419                         free(retval);
420                         retval = NULL;
421                         break;
422                     }
423                     retval = tmp;
424                 }
425                 memcpy(retval + (size - rc), buf, rc);
426             }
427         }
428         ov_clear(&vf);
429         return retval;
430     }
431
432     fclose(io);
433     return NULL;
434 }
435
436
437 AL_API OPENAL_SAMPLE *OPENAL_Sample_Load(int index, const char *name_or_data, unsigned int mode, int offset, int length)
438 {
439     if (!initialized)
440         return NULL;
441     if (index != OPENAL_FREE)
442         return NULL;  // this is all the game does...
443     if (offset != 0)
444         return NULL;  // this is all the game does...
445     if (length != 0)
446         return NULL;  // this is all the game does...
447     if ((mode != OPENAL_HW3D) && (mode != OPENAL_2D))
448         return NULL;  // this is all the game does...
449
450     OPENAL_SAMPLE *retval = NULL;
451     ALuint bufferName = 0;
452     ALenum format = AL_NONE;
453     ALsizei size = 0;
454     ALuint frequency = 0;
455     void *data = decode_to_pcm(name_or_data, format, size, frequency);
456     if (data == NULL)
457         return NULL;
458
459     ALuint bid = 0;
460     alGetError();
461     alGenBuffers(1, &bid);
462     if (alGetError() == AL_NO_ERROR) {
463         alBufferData(bid, format, data, size, frequency);
464         retval = new OPENAL_SAMPLE;
465         retval->bid = bid;
466         retval->mode = OPENAL_LOOP_OFF;
467         retval->is2d = (mode == OPENAL_2D);
468         retval->name = new char[strlen(name_or_data) + 1];
469         if (retval->name)
470             strcpy(retval->name, name_or_data);
471     }
472
473     free(data);
474     return(retval);
475 }
476
477 AL_API void OPENAL_Sample_Free(OPENAL_SAMPLE *sptr)
478 {
479     if (!initialized)
480         return;
481     if (sptr) {
482         for (int i = 0; i < num_channels; i++) {
483             if (impl_channels[i].sample == sptr) {
484                 alSourceStop(impl_channels[i].sid);
485                 alSourcei(impl_channels[i].sid, AL_BUFFER, 0);
486                 impl_channels[i].sample = NULL;
487             }
488         }
489         alDeleteBuffers(1, &sptr->bid);
490         delete[] sptr->name;
491         delete sptr;
492     }
493 }
494
495 static signed char OPENAL_Sample_SetMode(OPENAL_SAMPLE *sptr, unsigned int mode)
496 {
497     if (!initialized)
498         return false;
499     if ((mode != OPENAL_LOOP_NORMAL) && (mode != OPENAL_LOOP_OFF))
500         return false;
501     if (!sptr)
502         return false;
503     sptr->mode = mode;
504     return true;
505 }
506
507 AL_API signed char OPENAL_SetFrequency(int channel, int freq)
508 {
509     if (!initialized)
510         return false;
511     if (channel == OPENAL_ALL) {
512         for (int i = 0; i < num_channels; i++)
513             OPENAL_SetFrequency(i, freq);
514         return true;
515     }
516
517     if ((channel < 0) || (channel >= num_channels))
518         return false;
519     if (freq == 8012)
520         // hack
521         alSourcef(impl_channels[channel].sid, AL_PITCH, 8012.0f / 44100.0f);
522     else
523         alSourcef(impl_channels[channel].sid, AL_PITCH, 1.0f);
524     return true;
525 }
526
527 AL_API signed char OPENAL_SetVolume(int channel, int vol)
528 {
529     if (!initialized)
530         return false;
531
532     if (channel == OPENAL_ALL) {
533         for (int i = 0; i < num_channels; i++)
534             OPENAL_SetVolume(i, vol);
535         return true;
536     }
537
538     if ((channel < 0) || (channel >= num_channels))
539         return false;
540
541     if (vol < 0)
542         vol = 0;
543     else if (vol > 255)
544         vol = 255;
545     ALfloat gain = ((ALfloat) vol) / 255.0f;
546     alSourcef(impl_channels[channel].sid, AL_GAIN, gain);
547     return true;
548 }
549
550 AL_API signed char OPENAL_SetPaused(int channel, signed char paused)
551 {
552     if (!initialized)
553         return false;
554
555     if (channel == OPENAL_ALL) {
556         for (int i = 0; i < num_channels; i++)
557             OPENAL_SetPaused(i, paused);
558         return true;
559     }
560
561     if ((channel < 0) || (channel >= num_channels))
562         return false;
563
564     ALint state = 0;
565     if (impl_channels[channel].startpaused)
566         state = AL_PAUSED;
567     else
568         alGetSourceiv(impl_channels[channel].sid, AL_SOURCE_STATE, &state);
569
570     if ((paused) && (state == AL_PLAYING))
571         alSourcePause(impl_channels[channel].sid);
572     else if ((!paused) && (state == AL_PAUSED)) {
573         alSourcePlay(impl_channels[channel].sid);
574         impl_channels[channel].startpaused = false;
575     }
576     return true;
577 }
578
579 AL_API void OPENAL_SetSFXMasterVolume(int volume)
580 {
581     if (!initialized)
582         return;
583     ALfloat gain = ((ALfloat) volume) / 255.0f;
584     alListenerf(AL_GAIN, gain);
585 }
586
587 AL_API signed char OPENAL_StopSound(int channel)
588 {
589     if (!initialized)
590         return false;
591
592     if (channel == OPENAL_ALL) {
593         for (int i = 0; i < num_channels; i++)
594             OPENAL_StopSound(i);
595         return true;
596     }
597
598     if ((channel < 0) || (channel >= num_channels))
599         return false;
600     alSourceStop(impl_channels[channel].sid);
601     impl_channels[channel].startpaused = false;
602     return true;
603 }
604
605 AL_API void OPENAL_Stream_Close(OPENAL_STREAM *stream)
606 {
607     OPENAL_Sample_Free((OPENAL_SAMPLE *) stream);
608 }
609
610 static OPENAL_SAMPLE *OPENAL_Stream_GetSample(OPENAL_STREAM *stream)
611 {
612     if (!initialized)
613         return NULL;
614     return (OPENAL_SAMPLE *) stream;
615 }
616
617 static int OPENAL_Stream_PlayEx(int channel, OPENAL_STREAM *stream, OPENAL_DSPUNIT *dsp, signed char startpaused)
618 {
619     return OPENAL_PlaySoundEx(channel, (OPENAL_SAMPLE *) stream, dsp, startpaused);
620 }
621
622 static signed char OPENAL_Stream_Stop(OPENAL_STREAM *stream)
623 {
624     if (!initialized)
625         return false;
626     for (int i = 0; i < num_channels; i++) {
627         if (impl_channels[i].sample == (OPENAL_SAMPLE *) stream) {
628             alSourceStop(impl_channels[i].sid);
629             impl_channels[i].startpaused = false;
630         }
631     }
632     return true;
633 }
634
635 AL_API signed char OPENAL_Stream_SetMode(OPENAL_STREAM *stream, unsigned int mode)
636 {
637     return OPENAL_Sample_SetMode((OPENAL_SAMPLE *) stream, mode);
638 }
639
640 AL_API void OPENAL_Update()
641 {
642     if (!initialized)
643         return;
644     alcProcessContext(alcGetCurrentContext());
645 }
646
647 AL_API signed char OPENAL_SetOutput(int outputtype)
648 {
649     return true;
650 }
651
652 extern int channels[];
653
654 extern "C" void PlaySoundEx(int chan, OPENAL_SAMPLE *sptr, OPENAL_DSPUNIT *dsp, signed char startpaused)
655 {
656     const OPENAL_SAMPLE * currSample = OPENAL_GetCurrentSample(channels[chan]);
657     if (currSample && currSample == samp[chan]) {
658         if (OPENAL_GetPaused(channels[chan])) {
659             OPENAL_StopSound(channels[chan]);
660             channels[chan] = OPENAL_FREE;
661         } else if (OPENAL_IsPlaying(channels[chan])) {
662             int loop_mode = OPENAL_GetLoopMode(channels[chan]);
663             if (loop_mode & OPENAL_LOOP_OFF) {
664                 channels[chan] = OPENAL_FREE;
665             }
666         }
667     } else {
668         channels[chan] = OPENAL_FREE;
669     }
670
671     channels[chan] = OPENAL_PlaySoundEx(channels[chan], sptr, dsp, startpaused);
672     if (channels[chan] < 0) {
673         channels[chan] = OPENAL_PlaySoundEx(OPENAL_FREE, sptr, dsp, startpaused);
674     }
675 }
676
677 extern "C" void PlayStreamEx(int chan, OPENAL_STREAM *sptr, OPENAL_DSPUNIT *dsp, signed char startpaused)
678 {
679     const OPENAL_SAMPLE * currSample = OPENAL_GetCurrentSample(channels[chan]);
680     if (currSample && currSample == OPENAL_Stream_GetSample(sptr)) {
681         OPENAL_StopSound(channels[chan]);
682         OPENAL_Stream_Stop(sptr);
683     } else {
684         OPENAL_Stream_Stop(sptr);
685         channels[chan] = OPENAL_FREE;
686     }
687
688     channels[chan] = OPENAL_Stream_PlayEx(channels[chan], sptr, dsp, startpaused);
689     if (channels[chan] < 0) {
690         channels[chan] = OPENAL_Stream_PlayEx(OPENAL_FREE, sptr, dsp, startpaused);
691     }
692 }