Download
926 lines 22.9 KiBC++Modified 2026-01-21 raw
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
/**
 * Copyright (c) 2006-2026 LOVE Development Team
 *
 * This software is provided 'as-is', without any express or implied
 * warranty.  In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 *    claim that you wrote the original software. If you use this software
 *    in a product, an acknowledgment in the product documentation would be
 *    appreciated but is not required.
 * 2. Altered source versions must be plainly marked as such, and must not be
 *    misrepresented as being the original software.
 * 3. This notice may not be removed or altered from any source distribution.
 **/

#include "Audio.h"
#include "common/delay.h"
#include "RecordingDevice.h"
#include "sound/Decoder.h"

#include <cstdlib>
#include <iostream>

#ifdef LOVE_IOS
#include "common/ios.h"
#endif

namespace love
{
namespace audio
{
namespace openal
{

Audio::PoolThread::PoolThread(Pool *pool)
	: pool(pool)
	, finish(false)
{
	threadName = "AudioPool";
}

Audio::PoolThread::~PoolThread()
{
}


void Audio::PoolThread::threadFunction()
{
	while (true)
	{
		{
			thread::Lock lock(mutex);
			if (finish)
			{
				return;
			}
		}

		pool->update();
		sleep(5);
	}
}

void Audio::PoolThread::setFinish()
{
	thread::Lock lock(mutex);
	finish = true;
}

ALenum Audio::getFormat(int bitDepth, int channels)
{
	if (bitDepth != 8 && bitDepth != 16)
		return AL_NONE;

	if (channels == 1)
		return bitDepth == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16;
	else if (channels == 2)
		return bitDepth == 8 ? AL_FORMAT_STEREO8 : AL_FORMAT_STEREO16;
#ifdef AL_EXT_MCFORMATS
	else if (alIsExtensionPresent("AL_EXT_MCFORMATS"))
	{
		if (channels == 6)
			return bitDepth == 8 ? AL_FORMAT_51CHN8 : AL_FORMAT_51CHN16;
		else if (channels == 8)
			return bitDepth == 8 ? AL_FORMAT_71CHN8 : AL_FORMAT_71CHN16;
	}
#endif
	return AL_NONE;
}

static const char *getDeviceSpecifier(ALCdevice *device)
{
#ifndef ALC_ALL_DEVICES_SPECIFIER
	constexpr ALCenum ALC_ALL_DEVICES_SPECIFIER = 0x1013;
#endif
	static ALCenum deviceEnum = alcIsExtensionPresent(nullptr, "ALC_ENUMERATE_ALL_EXT") == ALC_TRUE
		? ALC_ALL_DEVICES_SPECIFIER
		: ALC_DEVICE_SPECIFIER;

	return alcGetString(device, deviceEnum);
}

Audio::Audio()
	: love::audio::Audio("love.audio.openal")
	, device(nullptr)
	, context(nullptr)
	, pool(nullptr)
	, poolThread(nullptr)
	, distanceModel(DISTANCE_INVERSE_CLAMPED)
{
	// Before opening new device, check if recording
	// is requested.
	if (getRequestRecordingPermission())
	{
		if (!hasRecordingPermission())
			// Request recording permission on some OSes.
			requestRecordingPermission();
	}

	{
#if defined(LOVE_LINUX)
		// Temporarly block signals, as the thread inherits this mask
		love::thread::ScopedDisableSignals disableSignals;
#endif

		// Passing null for default device.
		device = alcOpenDevice(nullptr);

		if (device == nullptr)
			throw love::Exception("Could not open device.");

		hasHRTFExtension = alcIsExtensionPresent(device, "ALC_SOFT_HRTF") == AL_TRUE;

		std::vector<ALint> attribs = computeContextAttribs();

		context = alcCreateContext(device, attribs.data());

		if (context == nullptr)
			throw love::Exception("Could not create context: %s", alcGetString(device, alcGetError(device)));

		if (!alcMakeContextCurrent(context))
			throw love::Exception("Could not make context current: %s", alcGetString(device, alcGetError(device)));
	}

#ifdef ALC_EXT_EFX
	initializeEFX();

	alcGetIntegerv(device, ALC_MAX_AUXILIARY_SENDS, 1, &MAX_SOURCE_EFFECTS);

	alGetError();
	if (alGenAuxiliaryEffectSlots)
	{
		for (int i = 0; i < MAX_SCENE_EFFECTS; i++)
		{
			ALuint slot;
			alGenAuxiliaryEffectSlots(1, &slot);
			if (alGetError() == AL_NO_ERROR)
				slotlist.push(slot);
			else
			{
				MAX_SCENE_EFFECTS = i;
				break;
			}
		}
	}
	else
		MAX_SCENE_EFFECTS = MAX_SOURCE_EFFECTS = 0;
#else
	MAX_SCENE_EFFECTS = MAX_SOURCE_EFFECTS = 0;
#endif

	try
	{
		pool = new Pool(device);
	}
	catch (love::Exception &)
	{
		for (auto c : capture)
			delete c;

#ifdef ALC_EXT_EFX
		if (alDeleteAuxiliaryEffectSlots)
		{
			while (!slotlist.empty())
			{
				alDeleteAuxiliaryEffectSlots(1, &slotlist.top());
				slotlist.pop();
			}
		}
#endif

		alcMakeContextCurrent(nullptr);
		alcDestroyContext(context);
		alcCloseDevice(device);
		throw;
	}

	poolThread = new PoolThread(pool);
	poolThread->start();
	
#ifdef LOVE_IOS
	love::ios::initAudioSessionInterruptionHandler();
#endif

#ifdef LOVE_ANDROID
	bool hasPauseDeviceExt = alcIsExtensionPresent(device, "ALC_SOFT_pause_device") == ALC_TRUE;
	alcDevicePauseSOFT = hasPauseDeviceExt
		? (LPALCDEVICEPAUSESOFT) alcGetProcAddress(device, "alcDevicePauseSOFT")
		: nullptr;
	alcDeviceResumeSOFT = hasPauseDeviceExt
		? (LPALCDEVICERESUMESOFT) alcGetProcAddress(device, "alcDeviceResumeSOFT")
		: nullptr;
#endif
}

Audio::~Audio()
{
#ifdef LOVE_IOS
	love::ios::destroyAudioSessionInterruptionHandler();
#endif
	poolThread->setFinish();
	poolThread->wait();

	delete poolThread;
	delete pool;

	for (auto c : capture)
		delete c;

#ifdef ALC_EXT_EFX
	for (auto e : effectmap)
	{
		delete e.second.effect;
		slotlist.push(e.second.slot);
	}

	if (alDeleteAuxiliaryEffectSlots)
	{
		while (!slotlist.empty())
		{
			alDeleteAuxiliaryEffectSlots(1, &slotlist.top());
			slotlist.pop();
		}
	}
#endif
	alcMakeContextCurrent(nullptr);
	alcDestroyContext(context);
	alcCloseDevice(device);
}

std::vector<ALint> Audio::computeContextAttribs()
{
	std::vector<ALint> attribs;

#ifdef ALC_EXT_EFX
	attribs.push_back(ALC_MAX_AUXILIARY_SENDS);
	attribs.push_back(MAX_REQUESTED_SOURCE_EFFECTS);
#endif

#ifdef ALC_SOFT_HRTF
	if (hasHRTFExtension)
	{
		attribs.push_back(ALC_HRTF_SOFT);
		attribs.push_back(requestEnableHRTF ? AL_TRUE : AL_FALSE);
		if (!requestedHRTFFilter.empty())
		{
			std::vector<std::string> filters;
			getOutputSpatializationFilters(filters);

			for (size_t i = 0; i < filters.size(); i++)
			{
				if (filters[i] == requestedHRTFFilter)
				{
					attribs.push_back(ALC_HRTF_ID_SOFT);
					attribs.push_back((int)i);
					break;
				}
			}
		}
	}
#endif

	attribs.push_back(0);
	attribs.push_back(0);

	return attribs;
}

love::audio::Source *Audio::newSource(love::sound::Decoder *decoder)
{
	return new Source(pool, decoder);
}

love::audio::Source *Audio::newSource(love::sound::SoundData *soundData)
{
	return new Source(pool, soundData);
}

love::audio::Source *Audio::newSource(int sampleRate, int bitDepth, int channels, int buffers)
{
	return new Source(pool, sampleRate, bitDepth, channels, buffers);
}

int Audio::getActiveSourceCount() const
{
	return pool->getActiveSourceCount();
}

int Audio::getMaxSources() const
{
	return pool->getMaxSources();
}

bool Audio::play(love::audio::Source *source)
{
	return source->play();
}

bool Audio::play(const std::vector<love::audio::Source*> &sources)
{
	return Source::play(sources);
}

void Audio::stop(love::audio::Source *source)
{
	source->stop();
}

void Audio::stop(const std::vector<love::audio::Source*> &sources)
{
	return Source::stop(sources);
}

void Audio::stop()
{
	return Source::stop(pool);
}

void Audio::pause(love::audio::Source *source)
{
	source->pause();
}

void Audio::pause(const std::vector<love::audio::Source*> &sources)
{
	return Source::pause(sources);
}

std::vector<love::audio::Source*> Audio::pause()
{
	return Source::pause(pool);
}

void Audio::pauseContext()
{
#ifdef LOVE_ANDROID
	if (alcDevicePauseSOFT)
		alcDevicePauseSOFT(device);
	else
	{
		// This is extremely rare case since we're using OpenAL-soft
		// in Android and the ALC_SOFT_pause_device has been supported
		// since 1.16
		for (auto &src: pausedSources)
			src->release();
		pausedSources = pause();
		for (auto &src: pausedSources)
			src->retain();
	}
#else
	alcMakeContextCurrent(nullptr);
#endif
}

void Audio::resumeContext()
{
#ifdef LOVE_ANDROID
	if (alcDeviceResumeSOFT)
		alcDeviceResumeSOFT(device);
	else
	{
		// Again, this is rare case
		play(pausedSources);
		for (auto &src: pausedSources)
			src->release();
		pausedSources.resize(0);
	}
#else
	if (context && alcGetCurrentContext() != context)
		alcMakeContextCurrent(context);
#endif
}

std::string Audio::getPlaybackDevice()
{
	const char *dev = getDeviceSpecifier(device);

	if (dev == nullptr)
		throw Exception("Failed to get current device: %s", alcGetString(device, alcGetError(device)));

	return dev;
}

void Audio::getPlaybackDevices(std::vector<std::string> &list)
{
	const char *devices = getDeviceSpecifier(nullptr);

	if (devices == nullptr)
		throw Exception("Failed to enumerate devices: %s", alcGetString(nullptr, alcGetError(nullptr)));

	for (const char *device = devices; *device; device++)
	{
		list.emplace_back(device);
		device += list.back().length();
	}
}

void Audio::setPlaybackDevice(const char *name)
{
#ifndef ALC_SOFT_reopen_device
	typedef ALCboolean (ALC_APIENTRY*LPALCREOPENDEVICESOFT)(ALCdevice *device,
		const ALCchar *deviceName, const ALCint *attribs);
#endif
	static LPALCREOPENDEVICESOFT alcReopenDeviceSOFT = alcIsExtensionPresent(device, "ALC_SOFT_reopen_device") == ALC_TRUE
		? (LPALCREOPENDEVICESOFT) alcGetProcAddress(device, "alcReopenDeviceSOFT")
		: nullptr;

	if (alcReopenDeviceSOFT == nullptr)
	{
		// Default implementation throws exception. To make
		// error message consistent, call the base class.
		love::audio::Audio::setPlaybackDevice(name);
		return;
	}

	std::vector<ALint> attribs = computeContextAttribs();

	if (alcReopenDeviceSOFT(device, (const ALCchar *) name, attribs.data()) == ALC_FALSE)
		throw love::Exception("Cannot set output device: %s", alcGetString(device, alcGetError(device)));
}

void Audio::setVolume(float volume)
{
	alListenerf(AL_GAIN, volume);
}

float Audio::getVolume() const
{
	ALfloat volume;
	alGetListenerf(AL_GAIN, &volume);
	return volume;
}

void Audio::getPosition(float *v) const
{
	alGetListenerfv(AL_POSITION, v);
}

void Audio::setPosition(float *v)
{
	alListenerfv(AL_POSITION, v);
}

void Audio::getOrientation(float *v) const
{
	alGetListenerfv(AL_ORIENTATION, v);
}

void Audio::setOrientation(float *v)
{
	alListenerfv(AL_ORIENTATION, v);
}

void Audio::getVelocity(float *v) const
{
	alGetListenerfv(AL_VELOCITY, v);
}

void Audio::setVelocity(float *v)
{
	alListenerfv(AL_VELOCITY, v);
}

void Audio::setDopplerScale(float scale)
{
	if (scale >= 0.0f)
		alDopplerFactor(scale);
}

float Audio::getDopplerScale() const
{
	return alGetFloat(AL_DOPPLER_FACTOR);
}
/*
void Audio::setMeter(float scale)
{
	if (scale >= 0.0f)
	{
		metersPerUnit = scale;
#ifdef ALC_EXT_EFX
		alListenerf(AL_METERS_PER_UNIT, scale);
#endif
	}
}

float Audio::getMeter() const
{
	return metersPerUnit;
}
*/
Audio::DistanceModel Audio::getDistanceModel() const
{
	return distanceModel;
}

void Audio::setDistanceModel(DistanceModel distanceModel)
{
	this->distanceModel = distanceModel;

	switch (distanceModel)
	{
	case DISTANCE_NONE:
		alDistanceModel(AL_NONE);
		break;

	case DISTANCE_INVERSE:
		alDistanceModel(AL_INVERSE_DISTANCE);
		break;

	case DISTANCE_INVERSE_CLAMPED:
		alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED);
		break;

	case DISTANCE_LINEAR:
		alDistanceModel(AL_LINEAR_DISTANCE);
		break;

	case DISTANCE_LINEAR_CLAMPED:
		alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED);
		break;

	case DISTANCE_EXPONENT:
		alDistanceModel(AL_EXPONENT_DISTANCE);
		break;

	case DISTANCE_EXPONENT_CLAMPED:
		alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED);
		break;

	default:
		break;
	}
}

const std::vector<love::audio::RecordingDevice*> &Audio::getRecordingDevices()
{
	std::vector<std::string> devnames;
	std::vector<love::audio::RecordingDevice*> devices;

	// If recording permission is not granted, inform user about it
	// and return empty list.
	if (!hasRecordingPermission() && getRequestRecordingPermission())
	{
		showRecordingPermissionMissingDialog();
		capture.clear();
		return capture;
	}

	std::string defaultname(alcGetString(NULL, ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER));

	//no device name obtained from AL, fallback to reading from device
	if (defaultname.length() == 0)
	{
		//use some safe basic parameters - 8 kHz, 8 bits, 1 channel
		ALCdevice *defaultdevice = alcCaptureOpenDevice(NULL, 8000, AL_FORMAT_MONO8, 1024);
		if (alGetError() == AL_NO_ERROR)
		{
			defaultname = alcGetString(defaultdevice, ALC_CAPTURE_DEVICE_SPECIFIER);
			alcCaptureCloseDevice(defaultdevice);
		}
		else
		{
			//failed to open default recording device - bail, return empty list
			capture.clear();
			return capture;
		}
	}

	devnames.reserve(capture.size());
	devnames.push_back(defaultname);

	//find devices name list
	const ALCchar *devstr = alcGetString(NULL, ALC_CAPTURE_DEVICE_SPECIFIER);
	size_t offset = 0;
	while (true)
	{
		if (devstr[offset] == '\0')
			break;
		std::string str((ALCchar*)&devstr[offset]);
		if (str != defaultname)
			devnames.push_back(str);
		offset += str.length() + 1;
	}

	devices.reserve(devnames.size());
	//build ordered list of devices
	for (int i = 0; i < (int) devnames.size(); i++)
	{
		devices.push_back(nullptr);
		auto d = devices.end() - 1;

		for (auto c : capture)
			if (devnames[i] == c->getName())
				*d = c;

		if (*d == nullptr)
			*d = new RecordingDevice(devnames[i].c_str());
		else
			(*d)->retain();
	}

	for (auto c : capture)
		c->release();
	capture.clear();
	capture.reserve(devices.size());

	//this needs to be executed in specific order
	for (unsigned int i = 0; i < devnames.size(); i++)
		capture.push_back(devices[i]);

	return capture;
}

bool Audio::setEffect(const char *name, std::map<Effect::Parameter, float> &params)
{
	Effect *effect;
	ALuint slot;

	auto iter = effectmap.find(name);
	if (iter == effectmap.end())
	{
		//new effect needed but no more slots
		if (effectmap.size() >= (unsigned int)MAX_SCENE_EFFECTS)
			return false;

		effect = new Effect();
		slot = slotlist.top();
		slotlist.pop();

		effectmap[name] = {effect, slot};
	}
	else
	{
		effect = iter->second.effect;
		slot = iter->second.slot;
	}

	bool result = effect->setParams(params);

#ifdef ALC_EXT_EFX
	if (alAuxiliaryEffectSloti)
	{
		if (result)
		{
			auto iter = params.find(Effect::EFFECT_VOLUME);
			if (iter != params.end())
				alAuxiliaryEffectSlotf(slot, AL_EFFECTSLOT_GAIN, iter->second);
			alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, effect->getEffect());
		}
		else
			alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL);
		alGetError();
	}
#endif

	return result;
}

bool Audio::unsetEffect(const char *name)
{
	auto iter = effectmap.find(name);
	if (iter == effectmap.end())
		return false;

	Effect *effect = iter->second.effect;
	ALuint slot = iter->second.slot;

#ifdef ALC_EXT_EFX
	if (alAuxiliaryEffectSloti)
		alAuxiliaryEffectSloti(slot, AL_EFFECTSLOT_EFFECT, AL_EFFECT_NULL);
#endif

	delete effect;
	effectmap.erase(iter);
	slotlist.push(slot);
	return true;
}

bool Audio::getEffect(const char *name, std::map<Effect::Parameter, float> &params)
{
	auto iter = effectmap.find(name);
	if (iter == effectmap.end())
		return false;

	params = iter->second.effect->getParams();

	return true;
}

bool Audio::getActiveEffects(std::vector<std::string> &list) const
{
	if (effectmap.empty())
		return false;

	list.reserve(effectmap.size());
	for (auto i : effectmap)
		list.push_back(i.first);

	return true;
}

int Audio::getMaxSceneEffects() const
{
	return MAX_SCENE_EFFECTS;
}

int Audio::getMaxSourceEffects() const
{
	return MAX_SOURCE_EFFECTS;
}

bool Audio::isEFXsupported() const
{
#ifdef ALC_EXT_EFX
	return (alGenEffects != nullptr);
#else
	return false;
#endif
}

bool Audio::setOutputSpatialization(bool enable, const char *filter)
{
	requestEnableHRTF = enable;
	if (filter != nullptr)
		requestedHRTFFilter = filter;
	else
		requestedHRTFFilter.clear();

#ifdef ALC_SOFT_HRTF
	if (hasHRTFExtension)
	{
		static auto alcResetDeviceSOFT = (LPALCRESETDEVICESOFT)alcGetProcAddress(device, "alcResetDeviceSOFT");
		if (alcResetDeviceSOFT == nullptr)
			return false;

		std::vector<ALint> attribs = computeContextAttribs();
		return alcResetDeviceSOFT(device, attribs.data()) != AL_FALSE;
	}
#endif

	return false;
}

bool Audio::getOutputSpatialization(const char *&filter) const
{
#ifdef ALC_SOFT_HRTF
	if (hasHRTFExtension)
	{
		ALCint enabled = 0;
		alcGetIntegerv(device, ALC_HRTF_SOFT, 1, &enabled);
		if (enabled != 0)
			filter = alcGetString(device, ALC_HRTF_SPECIFIER_SOFT);
		else
			filter = nullptr;
		return enabled != 0;
	}
#endif

	filter = nullptr;
	return false;
}

void Audio::getOutputSpatializationFilters(std::vector<std::string> &list) const
{
#ifdef ALC_SOFT_HRTF
	if (!hasHRTFExtension)
		return;

	static auto alcGetStringiSOFT = (LPALCGETSTRINGISOFT)alcGetProcAddress(device, "alcGetStringiSOFT");
	if (alcGetStringiSOFT == nullptr)
		return;

	ALCint count = 0;
	alcGetIntegerv(device, ALC_NUM_HRTF_SPECIFIERS_SOFT, 1, &count);
	for (int i = 0; i < count; i++)
	{
		const char *specifier = alcGetStringiSOFT(device, ALC_HRTF_SPECIFIER_SOFT, i);
		if (specifier != nullptr)
			list.push_back(specifier);
	}
#else
	LOVE_UNUSED(list);
#endif
}

bool Audio::getEffectID(const char *name, ALuint &id)
{
	auto iter = effectmap.find(name);
	if (iter == effectmap.end())
		return false;

	id = iter->second.slot;
	return true;
}

#ifdef ALC_EXT_EFX
LPALGENEFFECTS alGenEffects = nullptr;
LPALDELETEEFFECTS alDeleteEffects = nullptr;
LPALISEFFECT alIsEffect = nullptr;
LPALEFFECTI alEffecti = nullptr;
LPALEFFECTIV alEffectiv = nullptr;
LPALEFFECTF alEffectf = nullptr;
LPALEFFECTFV alEffectfv = nullptr;
LPALGETEFFECTI alGetEffecti = nullptr;
LPALGETEFFECTIV alGetEffectiv = nullptr;
LPALGETEFFECTF alGetEffectf = nullptr;
LPALGETEFFECTFV alGetEffectfv = nullptr;
LPALGENFILTERS alGenFilters = nullptr;
LPALDELETEFILTERS alDeleteFilters = nullptr;
LPALISFILTER alIsFilter = nullptr;
LPALFILTERI alFilteri = nullptr;
LPALFILTERIV alFilteriv = nullptr;
LPALFILTERF alFilterf = nullptr;
LPALFILTERFV alFilterfv = nullptr;
LPALGETFILTERI alGetFilteri = nullptr;
LPALGETFILTERIV alGetFilteriv = nullptr;
LPALGETFILTERF alGetFilterf = nullptr;
LPALGETFILTERFV alGetFilterfv = nullptr;
LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots = nullptr;
LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots = nullptr;
LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot = nullptr;
LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti = nullptr;
LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv = nullptr;
LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf = nullptr;
LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv = nullptr;
LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti = nullptr;
LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv = nullptr;
LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf = nullptr;
LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv = nullptr;
#endif

void Audio::initializeEFX()
{
#ifdef ALC_EXT_EFX
	if (alcIsExtensionPresent(device, "ALC_EXT_EFX") == AL_FALSE)
		return;

	alGenEffects = (LPALGENEFFECTS)alGetProcAddress("alGenEffects");
	alDeleteEffects = (LPALDELETEEFFECTS)alGetProcAddress("alDeleteEffects");
	alIsEffect = (LPALISEFFECT)alGetProcAddress("alIsEffect");
	alEffecti = (LPALEFFECTI)alGetProcAddress("alEffecti");
	alEffectiv = (LPALEFFECTIV)alGetProcAddress("alEffectiv");
	alEffectf = (LPALEFFECTF)alGetProcAddress("alEffectf");
	alEffectfv = (LPALEFFECTFV)alGetProcAddress("alEffectfv");
	alGetEffecti = (LPALGETEFFECTI)alGetProcAddress("alGetEffecti");
	alGetEffectiv = (LPALGETEFFECTIV)alGetProcAddress("alGetEffectiv");
	alGetEffectf = (LPALGETEFFECTF)alGetProcAddress("alGetEffectf");
	alGetEffectfv = (LPALGETEFFECTFV)alGetProcAddress("alGetEffectfv");
	alGenFilters = (LPALGENFILTERS)alGetProcAddress("alGenFilters");
	alDeleteFilters = (LPALDELETEFILTERS)alGetProcAddress("alDeleteFilters");
	alIsFilter = (LPALISFILTER)alGetProcAddress("alIsFilter");
	alFilteri = (LPALFILTERI)alGetProcAddress("alFilteri");
	alFilteriv = (LPALFILTERIV)alGetProcAddress("alFilteriv");
	alFilterf = (LPALFILTERF)alGetProcAddress("alFilterf");
	alFilterfv = (LPALFILTERFV)alGetProcAddress("alFilterfv");
	alGetFilteri = (LPALGETFILTERI)alGetProcAddress("alGetFilteri");
	alGetFilteriv = (LPALGETFILTERIV)alGetProcAddress("alGetFilteriv");
	alGetFilterf = (LPALGETFILTERF)alGetProcAddress("alGetFilterf");
	alGetFilterfv = (LPALGETFILTERFV)alGetProcAddress("alGetFilterfv");
	alGenAuxiliaryEffectSlots = (LPALGENAUXILIARYEFFECTSLOTS)alGetProcAddress("alGenAuxiliaryEffectSlots");
	alDeleteAuxiliaryEffectSlots = (LPALDELETEAUXILIARYEFFECTSLOTS)alGetProcAddress("alDeleteAuxiliaryEffectSlots");
	alIsAuxiliaryEffectSlot = (LPALISAUXILIARYEFFECTSLOT)alGetProcAddress("alIsAuxiliaryEffectSlot");
	alAuxiliaryEffectSloti = (LPALAUXILIARYEFFECTSLOTI)alGetProcAddress("alAuxiliaryEffectSloti");
	alAuxiliaryEffectSlotiv = (LPALAUXILIARYEFFECTSLOTIV)alGetProcAddress("alAuxiliaryEffectSlotiv");
	alAuxiliaryEffectSlotf = (LPALAUXILIARYEFFECTSLOTF)alGetProcAddress("alAuxiliaryEffectSlotf");
	alAuxiliaryEffectSlotfv = (LPALAUXILIARYEFFECTSLOTFV)alGetProcAddress("alAuxiliaryEffectSlotfv");
	alGetAuxiliaryEffectSloti = (LPALGETAUXILIARYEFFECTSLOTI)alGetProcAddress("alGetAuxiliaryEffectSloti");
	alGetAuxiliaryEffectSlotiv = (LPALGETAUXILIARYEFFECTSLOTIV)alGetProcAddress("alGetAuxiliaryEffectSlotiv");
	alGetAuxiliaryEffectSlotf = (LPALGETAUXILIARYEFFECTSLOTF)alGetProcAddress("alGetAuxiliaryEffectSlotf");
	alGetAuxiliaryEffectSlotfv = (LPALGETAUXILIARYEFFECTSLOTFV)alGetProcAddress("alGetAuxiliaryEffectSlotfv");

	//failed to initialize functions, revert to nullptr
	if (!alGenEffects || !alDeleteEffects || !alIsEffect ||
		!alGenFilters || !alDeleteFilters || !alIsFilter ||
		!alGenAuxiliaryEffectSlots || !alDeleteAuxiliaryEffectSlots || !alIsAuxiliaryEffectSlot ||
		!alEffecti || !alEffectiv || !alEffectf || !alEffectfv ||
		!alGetEffecti || !alGetEffectiv || !alGetEffectf || !alGetEffectfv ||
		!alFilteri || !alFilteriv || !alFilterf || !alFilterfv ||
		!alGetFilteri || !alGetFilteriv || !alGetFilterf || !alGetFilterfv ||
		!alAuxiliaryEffectSloti || !alAuxiliaryEffectSlotiv || !alAuxiliaryEffectSlotf || !alAuxiliaryEffectSlotfv ||
		!alGetAuxiliaryEffectSloti || !alGetAuxiliaryEffectSlotiv || !alGetAuxiliaryEffectSlotf || !alGetAuxiliaryEffectSlotfv)
	{
		alGenEffects = nullptr; alDeleteEffects = nullptr; alIsEffect = nullptr;
		alEffecti = nullptr; alEffectiv = nullptr; alEffectf = nullptr; alEffectfv = nullptr;
		alGetEffecti = nullptr; alGetEffectiv = nullptr; alGetEffectf = nullptr; alGetEffectfv = nullptr;
		alGenFilters = nullptr; alDeleteFilters = nullptr; alIsFilter = nullptr;
		alFilteri = nullptr; alFilteriv = nullptr; alFilterf = nullptr; alFilterfv = nullptr;
		alGetFilteri = nullptr; alGetFilteriv = nullptr; alGetFilterf = nullptr; alGetFilterfv = nullptr;
		alGenAuxiliaryEffectSlots = nullptr; alDeleteAuxiliaryEffectSlots = nullptr; alIsAuxiliaryEffectSlot = nullptr;
		alAuxiliaryEffectSloti = nullptr; alAuxiliaryEffectSlotiv = nullptr;
		alAuxiliaryEffectSlotf = nullptr; alAuxiliaryEffectSlotfv = nullptr;
		alGetAuxiliaryEffectSloti = nullptr; alGetAuxiliaryEffectSlotiv = nullptr;
		alGetAuxiliaryEffectSlotf = nullptr; alGetAuxiliaryEffectSlotfv = nullptr;
	}

#endif
}

} // openal
} // audio
} // love