1/**
2 * Copyright (c) 2006-2023 LOVE Development Team
3 *
4 * This software is provided 'as-is', without any express or implied
5 * warranty. In no event will the authors be held liable for any damages
6 * arising from the use of this software.
7 *
8 * Permission is granted to anyone to use this software for any purpose,
9 * including commercial applications, and to alter it and redistribute it
10 * freely, subject to the following restrictions:
11 *
12 * 1. The origin of this software must not be misrepresented; you must not
13 * claim that you wrote the original software. If you use this software
14 * in a product, an acknowledgment in the product documentation would be
15 * appreciated but is not required.
16 * 2. Altered source versions must be plainly marked as such, and must not be
17 * misrepresented as being the original software.
18 * 3. This notice may not be removed or altered from any source distribution.
19 **/
20
21#include "Audio.h"
22#include "common/delay.h"
23#include "RecordingDevice.h"
24#include "sound/Decoder.h"
25
26#include <cstdlib>
27#include <iostream>
28
29#ifdef LOVE_IOS
30#include "common/ios.h"
31#endif
32
33namespace love
34{
35namespace audio
36{
37namespace openal
38{
39
40Audio::PoolThread::PoolThread(Pool *pool)
41 : pool(pool)
42 , finish(false)
43{
44 threadName = "AudioPool";
45}
46
47Audio::PoolThread::~PoolThread()
48{
49}
50
51
52void Audio::PoolThread::threadFunction()
53{
54 while (true)
55 {
56 {
57 thread::Lock lock(mutex);
58 if (finish)
59 {
60 return;
61 }
62 }
63
64 pool->update();
65 sleep(5);
66 }
67}
68
69void Audio::PoolThread::setFinish()
70{
71 thread::Lock lock(mutex);
72 finish = true;
73}
74
75ALenum Audio::getFormat(int bitDepth, int channels)
76{
77 if (bitDepth != 8 && bitDepth != 16)
78 return AL_NONE;
79
80 if (channels == 1)
81 return bitDepth == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
82 else if (channels == 2)
83 return bitDepth == 8 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
84#ifdef AL_EXT_MCFORMATS
85 else if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
86 {
87 if (channels == 6)
88 return bitDepth == 8 ? AL_FORMAT_51CHN8 : AL_FORMAT_51CHN16;
89 else if (channels == 8)
90 return bitDepth == 8 ? AL_FORMAT_71CHN8 : AL_FORMAT_71CHN16;
91 }
92#endif
93 return AL_NONE;
94}
95
96Audio::Audio()
97 : device(nullptr)
98 , context(nullptr)
99 , pool(nullptr)
100 , poolThread(nullptr)
101 , distanceModel(DISTANCE_INVERSE_CLAMPED)
102{
103 // Before opening new device, check if recording
104 // is requested.
105 if (getRequestRecordingPermission())
106 {
107 if (!hasRecordingPermission())
108 // Request recording permission on some OSes.
109 requestRecordingPermission();
110 }
111
112 {
113#if defined(LOVE_LINUX)
114 // Temporarly block signals, as the thread inherits this mask
115 love::thread::ScopedDisableSignals disableSignals;
116#endif
117
118 // Passing null for default device.
119 device = alcOpenDevice(nullptr);
120
121 if (device == nullptr)
122 throw love::Exception("Could not open device.");
123
124#ifdef ALC_EXT_EFX
125 ALint attribs[4] = { ALC_MAX_AUXILIARY_SENDS, MAX_SOURCE_EFFECTS, 0, 0 };
126#else
127 ALint *attribs = nullptr;
128#endif
129
130 context = alcCreateContext(device, attribs);
131
132 if (context == nullptr)
133 throw love::Exception("Could not create context.");
134
135 if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR)
136 throw love::Exception("Could not make context current.");
137 }
138
139#ifdef ALC_EXT_EFX
140 initializeEFX();
141
142 alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &MAX_SOURCE_EFFECTS);
143
144 alGetError();
145 if (alGenAuxiliaryEffectSlots)
146 {
147 for (int i = 0; i < MAX_SCENE_EFFECTS; i++)
148 {
149 ALuint slot;
150 alGenAuxiliaryEffectSlots(1, &slot);
151 if (alGetError() == AL_NO_ERROR)
152 slotlist.push(slot);
153 else
154 {
155 MAX_SCENE_EFFECTS = i;
156 break;
157 }
158 }
159 }
160 else
161 MAX_SCENE_EFFECTS = MAX_SOURCE_EFFECTS = 0;
162#else
163 MAX_SCENE_EFFECTS = MAX_SOURCE_EFFECTS = 0;
164#endif
165
166 try
167 {
168 pool = new Pool();
169 }
170 catch (love::Exception &)
171 {
172 for (auto c : capture)
173 delete c;
174
175#ifdef ALC_EXT_EFX
176 if (alDeleteAuxiliaryEffectSlots)
177 {
178 while (!slotlist.empty())
179 {
180 alDeleteAuxiliaryEffectSlots(1, &slotlist.top());
181 slotlist.pop();
182 }
183 }
184#endif
185
186 alcMakeContextCurrent(nullptr);
187 alcDestroyContext(context);
188 alcCloseDevice(device);
189 throw;
190 }
191
192 poolThread = new PoolThread(pool);
193 poolThread->start();
194
195#ifdef LOVE_IOS
196 love::ios::initAudioSessionInterruptionHandler();
197#endif
198
199#ifdef LOVE_ANDROID
200 bool hasPauseDeviceExt = alcIsExtensionPresent(device, "ALC_SOFT_pause_device") == ALC_TRUE;
201 alcDevicePauseSOFT = hasPauseDeviceExt
202 ? (LPALCDEVICEPAUSESOFT) alcGetProcAddress(device, "alcDevicePauseSOFT")
203 : nullptr;
204 alcDeviceResumeSOFT = hasPauseDeviceExt
205 ? (LPALCDEVICERESUMESOFT) alcGetProcAddress(device, "alcDeviceResumeSOFT")
206 : nullptr;
207#endif
208}
209
210Audio::~Audio()
211{
212#ifdef LOVE_IOS
213 love::ios::destroyAudioSessionInterruptionHandler();
214#endif
215 poolThread->setFinish();
216 poolThread->wait();
217
218 delete poolThread;
219 delete pool;
220
221 for (auto c : capture)
222 delete c;
223
224#ifdef ALC_EXT_EFX
225 for (auto e : effectmap)
226 {
227 delete e.second.effect;
228 slotlist.push(e.second.slot);
229 }
230
231 if (alDeleteAuxiliaryEffectSlots)
232 {
233 while (!slotlist.empty())
234 {
235 alDeleteAuxiliaryEffectSlots(1, &slotlist.top());
236 slotlist.pop();
237 }
238 }
239#endif
240 alcMakeContextCurrent(nullptr);
241 alcDestroyContext(context);
242 alcCloseDevice(device);
243}
244
245const char *Audio::getName() const
246{
247 return "love.audio.openal";
248}
249
250love::audio::Source *Audio::newSource(love::sound::Decoder *decoder)
251{
252 return new Source(pool, decoder);
253}
254
255love::audio::Source *Audio::newSource(love::sound::SoundData *soundData)
256{
257 return new Source(pool, soundData);
258}
259
260love::audio::Source *Audio::newSource(int sampleRate, int bitDepth, int channels, int buffers)
261{
262 return new Source(pool, sampleRate, bitDepth, channels, buffers);
263}
264
265int Audio::getActiveSourceCount() const
266{
267 return pool->getActiveSourceCount();
268}
269
270int Audio::getMaxSources() const
271{
272 return pool->getMaxSources();
273}
274
275bool Audio::play(love::audio::Source *source)
276{
277 return source->play();
278}
279
280bool Audio::play(const std::vector<love::audio::Source*> &sources)
281{
282 return Source::play(sources);
283}
284
285void Audio::stop(love::audio::Source *source)
286{
287 source->stop();
288}
289
290void Audio::stop(const std::vector<love::audio::Source*> &sources)
291{
292 return Source::stop(sources);
293}
294
295void Audio::stop()
296{
297 return Source::stop(pool);
298}
299
300void Audio::pause(love::audio::Source *source)
301{
302 source->pause();
303}
304
305void Audio::pause(const std::vector<love::audio::Source*> &sources)
306{
307 return Source::pause(sources);
308}
309
310std::vector<love::audio::Source*> Audio::pause()
311{
312 return Source::pause(pool);
313}
314
315void Audio::pauseContext()
316{
317#ifdef LOVE_ANDROID
318 if (alcDevicePauseSOFT)
319 alcDevicePauseSOFT(device);
320 else
321 {
322 // This is extremely rare case since we're using OpenAL-soft
323 // in Android and the ALC_SOFT_pause_device has been supported
324 // since 1.16
325 for (auto &src: pausedSources)
326 src->release();
327 pausedSources = pause();
328 for (auto &src: pausedSources)
329 src->retain();
330 }
331#else
332 alcMakeContextCurrent(nullptr);
333#endif
334}
335
336void Audio::resumeContext()
337{
338#ifdef LOVE_ANDROID
339 if (alcDeviceResumeSOFT)
340 alcDeviceResumeSOFT(device);
341 else
342 {
343 // Again, this is rare case
344 play(pausedSources);
345 for (auto &src: pausedSources)
346 src->release();
347 pausedSources.resize(0);
348 }
349#else
350 if (context && alcGetCurrentContext() != context)
351 alcMakeContextCurrent(context);
352#endif
353}
354
355void Audio::setVolume(float volume)
356{
357 alListenerf(AL_GAIN, volume);
358}
359
360float Audio::getVolume() const
361{
362 ALfloat volume;
363 alGetListenerf(AL_GAIN, &volume);
364 return volume;
365}
366
367void Audio::getPosition(float *v) const
368{
369 alGetListenerfv(AL_POSITION, v);
370}
371
372void Audio::setPosition(float *v)
373{
374 alListenerfv(AL_POSITION, v);
375}
376
377void Audio::getOrientation(float *v) const
378{
379 alGetListenerfv(AL_ORIENTATION, v);
380}
381
382void Audio::setOrientation(float *v)
383{
384 alListenerfv(AL_ORIENTATION, v);
385}
386
387void Audio::getVelocity(float *v) const
388{
389 alGetListenerfv(AL_VELOCITY, v);
390}
391
392void Audio::setVelocity(float *v)
393{
394 alListenerfv(AL_VELOCITY, v);
395}
396
397void Audio::setDopplerScale(float scale)
398{
399 if (scale >= 0.0f)
400 alDopplerFactor(scale);
401}
402
403float Audio::getDopplerScale() const
404{
405 return alGetFloat(AL_DOPPLER_FACTOR);
406}
407/*
408void Audio::setMeter(float scale)
409{
410 if (scale >= 0.0f)
411 {
412 metersPerUnit = scale;
413#ifdef ALC_EXT_EFX
414 alListenerf(AL_METERS_PER_UNIT, scale);
415#endif
416 }
417}
418
419float Audio::getMeter() const
420{
421 return metersPerUnit;
422}
423*/
424Audio::DistanceModel Audio::getDistanceModel() const
425{
426 return distanceModel;
427}
428
429void Audio::setDistanceModel(DistanceModel distanceModel)
430{
431 this->distanceModel = distanceModel;
432
433 switch (distanceModel)
434 {
435 case DISTANCE_NONE:
436 alDistanceModel(AL_NONE);
437 break;
438
439 case DISTANCE_INVERSE:
440 alDistanceModel(AL_INVERSE_DISTANCE);
441 break;
442
443 case DISTANCE_INVERSE_CLAMPED:
444 alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
445 break;
446
447 case DISTANCE_LINEAR:
448 alDistanceModel(AL_LINEAR_DISTANCE);
449 break;
450
451 case DISTANCE_LINEAR_CLAMPED:
452 alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
453 break;
454
455 case DISTANCE_EXPONENT:
456 alDistanceModel(AL_EXPONENT_DISTANCE);
457 break;
458
459 case DISTANCE_EXPONENT_CLAMPED:
460 alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED);
461 break;
462
463 default:
464 break;
465 }
466}
467
468const std::vector<love::audio::RecordingDevice*> &Audio::getRecordingDevices()
469{
470 std::vector<std::string> devnames;
471 std::vector<love::audio::RecordingDevice*> devices;
472
473 // If recording permission is not granted, inform user about it
474 // and return empty list.
475 if (!hasRecordingPermission() && getRequestRecordingPermission())
476 {
477 showRecordingPermissionMissingDialog();
478 capture.clear();
479 return capture;
480 }
481
482 std::string defaultname(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));
483
484 //no device name obtained from AL, fallback to reading from device
485 if (defaultname.length() == 0)
486 {
487 //use some safe basic parameters - 8 kHz, 8 bits, 1 channel
488 ALCdevice *defaultdevice = alcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO8, 1024);
489 if (alGetError() == AL_NO_ERROR)
490 {
491 defaultname = alcGetString(defaultdevice, ALC_CAPTURE_DEVICE_SPECIFIER);
492 alcCaptureCloseDevice(defaultdevice);
493 }
494 else
495 {
496 //failed to open default recording device - bail, return empty list
497 capture.clear();
498 return capture;
499 }
500 }
501
502 devnames.reserve(capture.size());
503 devnames.push_back(defaultname);
504
505 //find devices name list
506 const ALCchar *devstr = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
507 size_t offset = 0;
508 while (true)
509 {
510 if (devstr[offset] == '\0')
511 break;
512 std::string str((ALCchar*)&devstr[offset]);
513 if (str != defaultname)
514 devnames.push_back(str);
515 offset += str.length() + 1;
516 }
517
518 devices.reserve(devnames.size());
519 //build ordered list of devices
520 for (int i = 0; i < (int) devnames.size(); i++)
521 {
522 devices.push_back(nullptr);
523 auto d = devices.end() - 1;
524
525 for (auto c : capture)
526 if (devnames[i] == c->getName())
527 *d = c;
528
529 if (*d == nullptr)
530 *d = new RecordingDevice(devnames[i].c_str());
531 else
532 (*d)->retain();
533 }
534
535 for (auto c : capture)
536 c->release();
537 capture.clear();
538 capture.reserve(devices.size());
539
540 //this needs to be executed in specific order
541 for (unsigned int i = 0; i < devnames.size(); i++)
542 capture.push_back(devices[i]);
543
544 return capture;
545}
546
547bool Audio::setEffect(const char *name, std::map<Effect::Parameter, float> &params)
548{
549 Effect *effect;
550 ALuint slot;
551
552 auto iter = effectmap.find(name);
553 if (iter == effectmap.end())
554 {
555 //new effect needed but no more slots
556 if (effectmap.size() >= (unsigned int)MAX_SCENE_EFFECTS)
557 return false;
558
559 effect = new Effect();
560 slot = slotlist.top();
561 slotlist.pop();
562
563 effectmap[name] = {effect, slot};
564 }
565 else
566 {
567 effect = iter->second.effect;
568 slot = iter->second.slot;
569 }
570
571 bool result = effect->setParams(params);
572
573#ifdef ALC_EXT_EFX
574 if (alAuxiliaryEffectSloti)
575 {
576 if (result)
577 {
578 auto iter = params.find(Effect::EFFECT_VOLUME);
579 if (iter != params.end())
580 alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, iter->second);
581 alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect->getEffect());
582 }
583 else
584 alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL);
585 alGetError();
586 }
587#endif
588
589 return result;
590}
591
592bool Audio::unsetEffect(const char *name)
593{
594 auto iter = effectmap.find(name);
595 if (iter == effectmap.end())
596 return false;
597
598 Effect *effect = iter->second.effect;
599 ALuint slot = iter->second.slot;
600
601#ifdef ALC_EXT_EFX
602 if (alAuxiliaryEffectSloti)
603 alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL);
604#endif
605
606 delete effect;
607 effectmap.erase(iter);
608 slotlist.push(slot);
609 return true;
610}
611
612bool Audio::getEffect(const char *name, std::map<Effect::Parameter, float> &params)
613{
614 auto iter = effectmap.find(name);
615 if (iter == effectmap.end())
616 return false;
617
618 params = iter->second.effect->getParams();
619
620 return true;
621}
622
623bool Audio::getActiveEffects(std::vector<std::string> &list) const
624{
625 if (effectmap.empty())
626 return false;
627
628 list.reserve(effectmap.size());
629 for (auto i : effectmap)
630 list.push_back(i.first);
631
632 return true;
633}
634
635int Audio::getMaxSceneEffects() const
636{
637 return MAX_SCENE_EFFECTS;
638}
639
640int Audio::getMaxSourceEffects() const
641{
642 return MAX_SOURCE_EFFECTS;
643}
644
645bool Audio::isEFXsupported() const
646{
647#ifdef ALC_EXT_EFX
648 return (alGenEffects != nullptr);
649#else
650 return false;
651#endif
652}
653
654bool Audio::getEffectID(const char *name, ALuint &id)
655{
656 auto iter = effectmap.find(name);
657 if (iter == effectmap.end())
658 return false;
659
660 id = iter->second.slot;
661 return true;
662}
663
664#ifdef ALC_EXT_EFX
665LPALGENEFFECTS alGenEffects = nullptr;
666LPALDELETEEFFECTS alDeleteEffects = nullptr;
667LPALISEFFECT alIsEffect = nullptr;
668LPALEFFECTI alEffecti = nullptr;
669LPALEFFECTIV alEffectiv = nullptr;
670LPALEFFECTF alEffectf = nullptr;
671LPALEFFECTFV alEffectfv = nullptr;
672LPALGETEFFECTI alGetEffecti = nullptr;
673LPALGETEFFECTIV alGetEffectiv = nullptr;
674LPALGETEFFECTF alGetEffectf = nullptr;
675LPALGETEFFECTFV alGetEffectfv = nullptr;
676LPALGENFILTERS alGenFilters = nullptr;
677LPALDELETEFILTERS alDeleteFilters = nullptr;
678LPALISFILTER alIsFilter = nullptr;
679LPALFILTERI alFilteri = nullptr;
680LPALFILTERIV alFilteriv = nullptr;
681LPALFILTERF alFilterf = nullptr;
682LPALFILTERFV alFilterfv = nullptr;
683LPALGETFILTERI alGetFilteri = nullptr;
684LPALGETFILTERIV alGetFilteriv = nullptr;
685LPALGETFILTERF alGetFilterf = nullptr;
686LPALGETFILTERFV alGetFilterfv = nullptr;
687LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots = nullptr;
688LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots = nullptr;
689LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot = nullptr;
690LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti = nullptr;
691LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv = nullptr;
692LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf = nullptr;
693LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv = nullptr;
694LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti = nullptr;
695LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv = nullptr;
696LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf = nullptr;
697LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv = nullptr;
698#endif
699
700void Audio::initializeEFX()
701{
702#ifdef ALC_EXT_EFX
703 if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_FALSE)
704 return;
705
706 alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
707 alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
708 alIsEffect = (LPALISEFFECT)alGetProcAddress("alIsEffect");
709 alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
710 alEffectiv = (LPALEFFECTIV)alGetProcAddress("alEffectiv");
711 alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
712 alEffectfv = (LPALEFFECTFV)alGetProcAddress("alEffectfv");
713 alGetEffecti = (LPALGETEFFECTI)alGetProcAddress("alGetEffecti");
714 alGetEffectiv = (LPALGETEFFECTIV)alGetProcAddress("alGetEffectiv");
715 alGetEffectf = (LPALGETEFFECTF)alGetProcAddress("alGetEffectf");
716 alGetEffectfv = (LPALGETEFFECTFV)alGetProcAddress("alGetEffectfv");
717 alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
718 alDeleteFilters = (LPALDELETEFILTERS)alGetProcAddress("alDeleteFilters");
719 alIsFilter = (LPALISFILTER)alGetProcAddress("alIsFilter");
720 alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
721 alFilteriv = (LPALFILTERIV)alGetProcAddress("alFilteriv");
722 alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
723 alFilterfv = (LPALFILTERFV)alGetProcAddress("alFilterfv");
724 alGetFilteri = (LPALGETFILTERI)alGetProcAddress("alGetFilteri");
725 alGetFilteriv = (LPALGETFILTERIV)alGetProcAddress("alGetFilteriv");
726 alGetFilterf = (LPALGETFILTERF)alGetProcAddress("alGetFilterf");
727 alGetFilterfv = (LPALGETFILTERFV)alGetProcAddress("alGetFilterfv");
728 alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
729 alDeleteAuxiliaryEffectSlots = (LPALDELETEAUXILIARYEFFECTSLOTS)alGetProcAddress("alDeleteAuxiliaryEffectSlots");
730 alIsAuxiliaryEffectSlot = (LPALISAUXILIARYEFFECTSLOT)alGetProcAddress("alIsAuxiliaryEffectSlot");
731 alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
732 alAuxiliaryEffectSlotiv = (LPALAUXILIARYEFFECTSLOTIV)alGetProcAddress("alAuxiliaryEffectSlotiv");
733 alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
734 alAuxiliaryEffectSlotfv = (LPALAUXILIARYEFFECTSLOTFV)alGetProcAddress("alAuxiliaryEffectSlotfv");
735 alGetAuxiliaryEffectSloti = (LPALGETAUXILIARYEFFECTSLOTI)alGetProcAddress("alGetAuxiliaryEffectSloti");
736 alGetAuxiliaryEffectSlotiv = (LPALGETAUXILIARYEFFECTSLOTIV)alGetProcAddress("alGetAuxiliaryEffectSlotiv");
737 alGetAuxiliaryEffectSlotf = (LPALGETAUXILIARYEFFECTSLOTF)alGetProcAddress("alGetAuxiliaryEffectSlotf");
738 alGetAuxiliaryEffectSlotfv = (LPALGETAUXILIARYEFFECTSLOTFV)alGetProcAddress("alGetAuxiliaryEffectSlotfv");
739
740 //failed to initialize functions, revert to nullptr
741 if (!alGenEffects || !alDeleteEffects || !alIsEffect ||
742 !alGenFilters || !alDeleteFilters || !alIsFilter ||
743 !alGenAuxiliaryEffectSlots || !alDeleteAuxiliaryEffectSlots || !alIsAuxiliaryEffectSlot ||
744 !alEffecti || !alEffectiv || !alEffectf || !alEffectfv ||
745 !alGetEffecti || !alGetEffectiv || !alGetEffectf || !alGetEffectfv ||
746 !alFilteri || !alFilteriv || !alFilterf || !alFilterfv ||
747 !alGetFilteri || !alGetFilteriv || !alGetFilterf || !alGetFilterfv ||
748 !alAuxiliaryEffectSloti || !alAuxiliaryEffectSlotiv || !alAuxiliaryEffectSlotf || !alAuxiliaryEffectSlotfv ||
749 !alGetAuxiliaryEffectSloti || !alGetAuxiliaryEffectSlotiv || !alGetAuxiliaryEffectSlotf || !alGetAuxiliaryEffectSlotfv)
750 {
751 alGenEffects = nullptr; alDeleteEffects = nullptr; alIsEffect = nullptr;
752 alEffecti = nullptr; alEffectiv = nullptr; alEffectf = nullptr; alEffectfv = nullptr;
753 alGetEffecti = nullptr; alGetEffectiv = nullptr; alGetEffectf = nullptr; alGetEffectfv = nullptr;
754 alGenFilters = nullptr; alDeleteFilters = nullptr; alIsFilter = nullptr;
755 alFilteri = nullptr; alFilteriv = nullptr; alFilterf = nullptr; alFilterfv = nullptr;
756 alGetFilteri = nullptr; alGetFilteriv = nullptr; alGetFilterf = nullptr; alGetFilterfv = nullptr;
757 alGenAuxiliaryEffectSlots = nullptr; alDeleteAuxiliaryEffectSlots = nullptr; alIsAuxiliaryEffectSlot = nullptr;
758 alAuxiliaryEffectSloti = nullptr; alAuxiliaryEffectSlotiv = nullptr;
759 alAuxiliaryEffectSlotf = nullptr; alAuxiliaryEffectSlotfv = nullptr;
760 alGetAuxiliaryEffectSloti = nullptr; alGetAuxiliaryEffectSlotiv = nullptr;
761 alGetAuxiliaryEffectSlotf = nullptr; alGetAuxiliaryEffectSlotfv = nullptr;
762 }
763
764#endif
765}
766
767} // openal
768} // audio
769} // love
770