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