Browse Source

Implement audio filters

Use Source:setFilter(type, params...) to set a filter.
Use Source:setFilter() to detach the current filter.
Use Source:getFilter() to get the currently set filter (or nil).

Squashed from pull request 27, and the clean-pr27 branch in love-experiments.

--HG--
branch : minor
Raidho 8 years ago
parent
commit
332ba76e62

+ 4 - 0
CMakeLists.txt

@@ -316,6 +316,8 @@ set(LOVE_SRC_MODULE_AUDIO_ROOT
 	src/modules/audio/Source.h
 	src/modules/audio/RecordingDevice.cpp
 	src/modules/audio/RecordingDevice.h
+	src/modules/audio/Filter.cpp
+	src/modules/audio/Filter.h
 	src/modules/audio/wrap_Audio.cpp
 	src/modules/audio/wrap_Audio.h
 	src/modules/audio/wrap_Source.cpp
@@ -342,6 +344,8 @@ set(LOVE_SRC_MODULE_AUDIO_OPENAL
 	src/modules/audio/openal/Source.h
 	src/modules/audio/openal/RecordingDevice.cpp
 	src/modules/audio/openal/RecordingDevice.h
+	src/modules/audio/openal/Filter.cpp
+	src/modules/audio/openal/Filter.h
 )
 
 set(LOVE_SRC_MODULE_AUDIO

+ 1 - 0
changes.txt

@@ -25,6 +25,7 @@ Released: N/A
   * Added 'anisotropy' graphics limit.
   * Added a Mesh:attachAttribute variant that takes a different target attribute name.
   * Added Mesh:detachAttribute.
+  * Added Source filters: low gain, high gain and band pass.
 
   * Changed all color values to be in the range 0-1, rather than 0-255.
   * Changed love.graphics.print and friends to ignore carriage returns.

+ 79 - 0
src/modules/audio/Filter.cpp

@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2006-2016 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 "Filter.h"
+
+namespace love
+{
+namespace audio
+{
+
+Filter::Filter()
+{
+}
+
+Filter::~Filter()
+{
+}
+
+Filter::Type Filter::getType() const
+{
+	return type;
+}
+
+bool Filter::getConstant(const char *in, Type &out)
+{
+	return types.find(in, out);
+}
+
+bool Filter::getConstant(Type in, const char *&out)
+{
+	return types.find(in, out);
+}
+
+int Filter::getParameterCount(Type in)
+{
+	return parameterCount[in];
+}
+
+int Filter::getParameterCount()
+{
+	return parameterCount[TYPE_MAX_ENUM];
+}
+
+StringMap<Filter::Type, Filter::TYPE_MAX_ENUM>::Entry Filter::typeEntries[] =
+{
+	{"lowpass", Filter::TYPE_LOWPASS},
+	{"highpass", Filter::TYPE_HIGHPASS},
+	{"bandpass", Filter::TYPE_BANDPASS},
+};
+
+StringMap<Filter::Type, Filter::TYPE_MAX_ENUM> Filter::types(Filter::typeEntries, sizeof(Filter::typeEntries));
+
+std::map<Filter::Type, int> Filter::parameterCount =
+{
+	{Filter::TYPE_LOWPASS, 2},
+	{Filter::TYPE_HIGHPASS, 2},
+	{Filter::TYPE_BANDPASS, 3},
+	{Filter::TYPE_MAX_ENUM, 3},
+};
+
+} //audio
+} //love

+ 65 - 0
src/modules/audio/Filter.h

@@ -0,0 +1,65 @@
+/**
+ * Copyright (c) 2006-2016 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.
+ **/
+
+#ifndef LOVE_AUDIO_FILTERS_H
+#define LOVE_AUDIO_FILTERS_H
+
+#include "common/Object.h"
+#include "common/StringMap.h"
+#include <map>
+
+namespace love
+{
+namespace audio
+{
+
+class Filter
+{
+public:
+	enum Type
+	{
+		TYPE_LOWPASS,
+		TYPE_HIGHPASS,
+		TYPE_BANDPASS,
+		TYPE_MAX_ENUM
+	};
+
+	Filter();
+	virtual ~Filter();
+	Type getType() const;
+
+	static bool getConstant(const char *in, Type &out);
+	static bool getConstant(Type in, const char *&out);
+	static int getParameterCount(Type in);
+	static int getParameterCount();
+
+protected:
+	Type type;
+
+private:
+	static StringMap<Type, TYPE_MAX_ENUM>::Entry typeEntries[];
+	static StringMap<Type, TYPE_MAX_ENUM> types;
+	static std::map<Type, int> parameterCount;
+};
+
+} //audio
+} //love
+
+#endif //LOVE_AUDIO_FILTERS_H

+ 7 - 0
src/modules/audio/Source.h

@@ -24,6 +24,9 @@
 // LOVE
 #include "common/Object.h"
 #include "common/StringMap.h"
+#include "Filter.h"
+
+#include <vector>
 
 namespace love
 {
@@ -104,6 +107,10 @@ public:
 
 	virtual int getChannels() const = 0;
 
+	virtual bool setFilter(Filter::Type type, std::vector<float> &params) = 0;
+	virtual bool setFilter() = 0;
+	virtual bool getFilter(Filter::Type &type, std::vector<float> &params) = 0;
+
 	virtual int getFreeBufferCount() const = 0;
 	virtual bool queue(void *data, size_t length, int dataSampleRate, int dataBitDepth, int dataChannels) = 0;
 

+ 15 - 0
src/modules/audio/null/Source.cpp

@@ -227,6 +227,21 @@ bool Source::queue(void *, size_t, int, int, int)
 	return false;
 }
 
+bool Source::setFilter(love::audio::Filter::Type, std::vector<float> &)
+{
+	return false;
+}
+
+bool Source::setFilter()
+{
+	return false;
+}
+
+bool Source::getFilter(love::audio::Filter::Type &, std::vector<float> &)
+{
+	return false;
+}
+
 } // null
 } // audio
 } // love

+ 5 - 0
src/modules/audio/null/Source.h

@@ -24,6 +24,7 @@
 // LOVE
 #include "common/Object.h"
 #include "audio/Source.h"
+#include "audio/Filter.h"
 
 namespace love
 {
@@ -79,6 +80,10 @@ public:
 	virtual int getFreeBufferCount() const;
 	virtual bool queue(void *data, size_t length, int dataSampleRate, int dataBitDepth, int dataChannels);
 
+	virtual bool setFilter(love::audio::Filter::Type type, std::vector<float> &params);
+	virtual bool setFilter();
+	virtual bool getFilter(love::audio::Filter::Type &type, std::vector<float> &params);
+
 private:
 
 	float pitch;

+ 105 - 1
src/modules/audio/openal/Audio.cpp

@@ -110,7 +110,8 @@ Audio::Audio()
 	if (!alcMakeContextCurrent(context) || alcGetError(device) != ALC_NO_ERROR)
 		throw love::Exception("Could not make context current.");
 
-	// pool must be allocated after AL context.
+	initializeEFX();
+
 	try
 	{
 		pool = new Pool();
@@ -383,6 +384,109 @@ const std::vector<love::audio::RecordingDevice*> &Audio::getRecordingDevices()
 	return capture;
 }
 
+#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

+ 43 - 1
src/modules/audio/openal/Audio.h

@@ -30,6 +30,7 @@
 // LOVE
 #include "audio/Audio.h"
 #include "audio/RecordingDevice.h"
+#include "audio/Filter.h"
 #include "common/config.h"
 #include "sound/SoundData.h"
 
@@ -111,7 +112,7 @@ public:
 	void setDistanceModel(DistanceModel distanceModel);
 
 private:
-
+	void initializeEFX();
 	// The OpenAL device.
 	ALCdevice *device;
 
@@ -150,6 +151,47 @@ private:
 
 }; // Audio
 
+#ifdef ALC_EXT_EFX
+ // Effect objects
+extern LPALGENEFFECTS alGenEffects;
+extern LPALDELETEEFFECTS alDeleteEffects;
+extern LPALISEFFECT alIsEffect;
+extern LPALEFFECTI alEffecti;
+extern LPALEFFECTIV alEffectiv;
+extern LPALEFFECTF alEffectf;
+extern LPALEFFECTFV alEffectfv;
+extern LPALGETEFFECTI alGetEffecti;
+extern LPALGETEFFECTIV alGetEffectiv;
+extern LPALGETEFFECTF alGetEffectf;
+extern LPALGETEFFECTFV alGetEffectfv;
+
+//Filter objects
+extern LPALGENFILTERS alGenFilters;
+extern LPALDELETEFILTERS alDeleteFilters;
+extern LPALISFILTER alIsFilter;
+extern LPALFILTERI alFilteri;
+extern LPALFILTERIV alFilteriv;
+extern LPALFILTERF alFilterf;
+extern LPALFILTERFV alFilterfv;
+extern LPALGETFILTERI alGetFilteri;
+extern LPALGETFILTERIV alGetFilteriv;
+extern LPALGETFILTERF alGetFilterf;
+extern LPALGETFILTERFV alGetFilterfv;
+
+// Auxiliary slot object
+extern LPALGENAUXILIARYEFFECTSLOTS alGenAuxiliaryEffectSlots;
+extern LPALDELETEAUXILIARYEFFECTSLOTS alDeleteAuxiliaryEffectSlots;
+extern LPALISAUXILIARYEFFECTSLOT alIsAuxiliaryEffectSlot;
+extern LPALAUXILIARYEFFECTSLOTI alAuxiliaryEffectSloti;
+extern LPALAUXILIARYEFFECTSLOTIV alAuxiliaryEffectSlotiv;
+extern LPALAUXILIARYEFFECTSLOTF alAuxiliaryEffectSlotf;
+extern LPALAUXILIARYEFFECTSLOTFV alAuxiliaryEffectSlotfv;
+extern LPALGETAUXILIARYEFFECTSLOTI alGetAuxiliaryEffectSloti;
+extern LPALGETAUXILIARYEFFECTSLOTIV alGetAuxiliaryEffectSlotiv;
+extern LPALGETAUXILIARYEFFECTSLOTF alGetAuxiliaryEffectSlotf;
+extern LPALGETAUXILIARYEFFECTSLOTFV alGetAuxiliaryEffectSlotfv;
+#endif
+
 } // openal
 } // audio
 } // love

+ 136 - 0
src/modules/audio/openal/Filter.cpp

@@ -0,0 +1,136 @@
+/**
+ * Copyright (c) 2006-2016 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 "Filter.h"
+#include "common/Exception.h"
+
+namespace love
+{
+namespace audio
+{
+namespace openal
+{
+
+//clamp values silently to avoid randomly throwing errors due to implementation differences
+float clampf(float val, float min, float max)
+{
+	if      (val < min) val = min;
+	else if (val > max) val = max;
+	return val;
+}
+
+//base class
+Filter::Filter()
+{
+	#ifdef ALC_EXT_EFX
+	if (!alGenFilters)
+		return;
+
+	alGenFilters(1, &filter);
+	if (alGetError() != AL_NO_ERROR)
+		throw love::Exception("Failed to create sound Filter.");
+	#endif
+}
+
+Filter::Filter(const Filter &s)
+	: Filter()
+{
+	setParams(s.getType(), s.getParams());
+}
+
+Filter::~Filter()
+{
+	#ifdef ALC_EXT_EFX
+	if (filter != AL_FILTER_NULL)
+		alDeleteFilters(1, &filter);
+	#endif
+}
+
+Filter *Filter::clone()
+{
+	return new Filter(*this);
+}
+
+ALuint Filter::getFilter() const
+{
+	return filter;
+}
+
+bool Filter::setParams(Type type, const std::vector<float> &params)
+{
+	this->type = type;
+	this->params = params;
+
+	if (filter == AL_FILTER_NULL)
+		return false;
+
+	#ifdef ALC_EXT_EFX
+	switch (type)
+	{
+		case TYPE_LOWPASS:
+			alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_LOWPASS);
+			break;
+		case TYPE_HIGHPASS:
+			alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_HIGHPASS);
+			break;
+		case TYPE_BANDPASS:
+			alFilteri(filter, AL_FILTER_TYPE, AL_FILTER_BANDPASS);
+			break;
+		case TYPE_MAX_ENUM:
+			break;
+	}
+	//failed to make filter specific type - not supported etc.
+	if (alGetError() != AL_NO_ERROR)
+	{
+		filter = AL_FILTER_NULL;
+		return false;
+	}
+
+	switch (type)
+	{
+		case TYPE_LOWPASS:
+			alFilterf(filter, AL_LOWPASS_GAIN, clampf(params[0], AL_LOWPASS_MIN_GAIN, AL_LOWPASS_MAX_GAIN));
+			alFilterf(filter, AL_LOWPASS_GAINHF, clampf(params[1], AL_LOWPASS_MIN_GAINHF, AL_LOWPASS_MAX_GAINHF));
+			break;
+		case TYPE_HIGHPASS:
+			alFilterf(filter, AL_HIGHPASS_GAIN, clampf(params[0], AL_HIGHPASS_MIN_GAIN, AL_HIGHPASS_MAX_GAIN));
+			alFilterf(filter, AL_HIGHPASS_GAINLF, clampf(params[1], AL_HIGHPASS_MIN_GAINLF, AL_HIGHPASS_MAX_GAINLF));
+			break;
+		case TYPE_BANDPASS:
+			alFilterf(filter, AL_BANDPASS_GAIN, clampf(params[0], AL_BANDPASS_MIN_GAIN, AL_BANDPASS_MAX_GAIN));
+			alFilterf(filter, AL_BANDPASS_GAINLF, clampf(params[1], AL_BANDPASS_MIN_GAINLF, AL_BANDPASS_MAX_GAINLF));
+			alFilterf(filter, AL_BANDPASS_GAINHF, clampf(params[2], AL_BANDPASS_MIN_GAINHF, AL_BANDPASS_MAX_GAINHF));
+			break;
+		case TYPE_MAX_ENUM:
+			break;
+	}
+	#endif
+
+	return true;
+}
+
+const std::vector<float> &Filter::getParams() const
+{
+	return params;
+}
+
+} //openal
+} //audio
+} //love

+ 75 - 0
src/modules/audio/openal/Filter.h

@@ -0,0 +1,75 @@
+/**
+ * Copyright (c) 2006-2016 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.
+ **/
+
+#ifndef LOVE_AUDIO_OPENAL_FILTERS_H
+#define LOVE_AUDIO_OPENAL_FILTERS_H
+
+// OpenAL
+#ifdef LOVE_APPLE_USE_FRAMEWORKS // Frameworks have different include paths.
+#ifdef LOVE_IOS
+#include <OpenAL/alc.h>
+#include <OpenAL/al.h>
+#else
+#include <OpenAL-Soft/alc.h>
+#include <OpenAL-Soft/al.h>
+#endif
+#else
+#include <AL/alc.h>
+#include <AL/al.h>
+#include <AL/alext.h>
+#endif
+
+#include <vector>
+
+#include "audio/Filter.h"
+#include "Audio.h"
+
+#ifndef AL_FILTER_NULL
+#define AL_FILTER_NULL (0)
+#endif
+
+namespace love
+{
+namespace audio
+{
+namespace openal
+{
+
+class Filter : public love::audio::Filter
+{
+public:
+	Filter();
+	Filter(const Filter &s);
+	virtual ~Filter();
+	virtual Filter *clone();
+	ALuint getFilter() const;
+	virtual bool setParams(Type type, const std::vector<float> &params);
+	virtual const std::vector<float> &getParams() const;
+
+private:
+	ALuint filter = AL_FILTER_NULL;
+	std::vector<float> params;
+};
+
+} //openal
+} //audio
+} //love
+
+#endif //LOVE_AUDIO_OPENAL_FILTERS_H

+ 49 - 0
src/modules/audio/openal/Source.cpp

@@ -19,6 +19,7 @@
  **/
 
 #include "Source.h"
+#include "Filter.h"
 #include "Pool.h"
 #include "Audio.h"
 #include "common/math.h"
@@ -213,6 +214,8 @@ Source::Source(const Source &s)
 		for (unsigned int i = 0; i < MAX_BUFFERS; i++)
 			unusedBuffers[i] = streamBuffers[i];
 	}
+	if (s.filter)
+		filter = s.filter->clone();
 
 	setFloatv(position, s.position);
 	setFloatv(velocity, s.velocity);
@@ -226,6 +229,9 @@ Source::~Source()
 
 	if (sourceType != TYPE_STATIC)
 		alDeleteBuffers(MAX_BUFFERS, streamBuffers);
+
+	if (filter)
+		delete filter;
 }
 
 love::audio::Source *Source::clone()
@@ -989,6 +995,9 @@ void Source::reset()
 	alSourcei(source, AL_CONE_INNER_ANGLE, cone.innerAngle);
 	alSourcei(source, AL_CONE_OUTER_ANGLE, cone.outerAngle);
 	alSourcef(source, AL_CONE_OUTER_GAIN, cone.outerVolume);
+	#ifdef ALC_EXT_EFX
+	alSourcei(source, AL_DIRECT_FILTER, filter ? filter->getFilter() : AL_FILTER_NULL);
+	#endif
 }
 
 void Source::setFloatv(float *dst, const float *src) const
@@ -1195,6 +1204,46 @@ int Source::getChannels() const
 	return channels;
 }
 
+bool Source::setFilter(love::audio::Filter::Type type, std::vector<float> &params)
+{
+	if (!filter)
+		filter = new Filter();
+
+	bool result = filter->setParams(type, params);
+
+	#ifdef ALC_EXT_EFX
+	if (valid)
+		alSourcei(source, AL_DIRECT_FILTER, filter->getFilter());
+	#endif
+
+	return result;
+}
+
+bool Source::setFilter()
+{
+	if (filter)
+		delete filter;
+
+	filter = nullptr;
+
+	#ifdef ALC_EXT_EFX
+	if (valid)
+		alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL);
+	#endif
+
+	return true;
+}
+
+bool Source::getFilter(love::audio::Filter::Type &type, std::vector<float> &params)
+{
+	if (!filter)
+		return false;
+
+	type = filter->getType();
+	params = filter->getParams();
+	return true;
+}
+
 } // openal
 } // audio
 } // love

+ 9 - 0
src/modules/audio/openal/Source.h

@@ -25,8 +25,11 @@
 #include "common/config.h"
 #include "common/Object.h"
 #include "audio/Source.h"
+#include "audio/Filter.h"
 #include "sound/SoundData.h"
 #include "sound/Decoder.h"
+#include "Audio.h"
+#include "Filter.h"
 
 // STL
 #include <vector>
@@ -141,6 +144,10 @@ public:
 	virtual float getMaxDistance() const;
 	virtual int getChannels() const;
 
+	virtual bool setFilter(love::audio::Filter::Type type, std::vector<float> &params);
+	virtual bool setFilter();
+	virtual bool getFilter(love::audio::Filter::Type &type, std::vector<float> &params);
+
 	virtual int getFreeBufferCount() const;
 	virtual bool queue(void *data, size_t length, int dataSampleRate, int dataBitDepth, int dataChannels);
 	virtual bool queueAtomic(void *data, ALsizei length);
@@ -214,6 +221,8 @@ private:
 	int unusedBufferTop = -1;
 	ALsizei bufferedBytes = 0;
 
+	Filter *filter = nullptr;
+
 }; // Source
 
 } // openal

+ 73 - 0
src/modules/audio/wrap_Source.cpp

@@ -330,6 +330,76 @@ int w_Source_getChannels(lua_State *L)
 	return 1;
 }
 
+int w_Source_setFilter(lua_State *L)
+{
+	Source *t = luax_checksource(L, 1);
+
+	Filter::Type type;
+	std::vector<float> params;
+
+	params.reserve(Filter::getParameterCount());
+
+	if (lua_gettop(L) == 1)
+	{
+		lua_pushboolean(L, t->setFilter());
+		return 1;
+	}
+	else if (lua_gettop(L) > 2)
+	{
+		const char *ftypestr = luaL_checkstring(L, 2);
+		if (ftypestr && !Filter::getConstant(ftypestr, type))
+			return luaL_error(L, "Invalid filter type: %s", ftypestr);
+
+		unsigned int count = Filter::getParameterCount(type);
+		for (unsigned int i = 0; i < count; i++)
+			params.push_back(luaL_checknumber(L, i + 3));
+	}
+	else if (lua_istable(L, 2))
+	{
+		if (lua_objlen(L, 2) == 0) //empty table also clears filter
+		{
+			lua_pushboolean(L, t->setFilter());
+			return 1;
+		}
+		lua_rawgeti(L, 2, 1);
+		const char *ftypestr = luaL_checkstring(L, -1);
+		if (ftypestr && !Filter::getConstant(ftypestr, type))
+			return luaL_error(L, "Invalid filter type: %s", ftypestr);
+		lua_pop(L, 1);
+
+		unsigned int count = Filter::getParameterCount(type);
+		for (unsigned int i = 0; i < count; i++)
+		{
+			lua_rawgeti(L, 2, i + 2);
+			params.push_back(luaL_checknumber(L, -1));
+			lua_pop(L, 1);
+		}
+	}
+	else
+		return luax_typerror(L, 2, "filter description");
+
+	luax_catchexcept(L, [&]() { lua_pushboolean(L, t->setFilter(type, params)); });
+	return 1;
+}
+
+int w_Source_getFilter(lua_State *L)
+{
+	Source *t = luax_checksource(L, 1);
+	Filter::Type type;
+	std::vector<float> params;
+	if (!t->getFilter(type, params))
+		return 0;
+
+	const char *str = nullptr;
+	Filter::getConstant(type, str);
+	lua_pushstring(L, str);
+
+	for (unsigned int i = 0; i < params.size(); i++)
+		lua_pushnumber(L, params[i]);
+
+	return params.size() + 1;
+}
+
 int w_Source_getFreeBufferCount(lua_State *L)
 {
 	Source *t = luax_checksource(L, 1);
@@ -440,6 +510,9 @@ static const luaL_Reg w_Source_functions[] =
 
 	{ "getChannels", w_Source_getChannels },
 
+	{ "setFilter", w_Source_setFilter },
+	{ "getFilter", w_Source_getFilter },
+
 	{ "getFreeBufferCount", w_Source_getFreeBufferCount },
 	{ "queue", w_Source_queue },
 

+ 1 - 0
src/modules/audio/wrap_Source.h

@@ -23,6 +23,7 @@
 
 #include "common/runtime.h"
 #include "Source.h"
+#include "Filter.h"
 
 namespace love
 {