text: /*
  ** oalsound.cpp
  ** System interface for sound; uses OpenAL
  **
  **---------------------------------------------------------------------------
  ** Copyright 2008-2010 Chris Robinson
  ** All rights reserved.
  **
  ** Redistribution and use in source and binary forms, with or without
  ** modification, are permitted provided that the following conditions
  ** are met:
  **
  ** 1. Redistributions of source code must retain the above copyright
  **    notice, this list of conditions and the following disclaimer.
  ** 2. Redistributions in binary form must reproduce the above copyright
  **    notice, this list of conditions and the following disclaimer in the
  **    documentation and/or other materials provided with the distribution.
  ** 3. The name of the author may not be used to endorse or promote products
  **    derived from this software without specific prior written permission.
  **
  ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
  ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
  ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
  ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  **---------------------------------------------------------------------------
  **
  */
  
  #include <functional>
  #include <memory>
  #include <chrono>
  
  #include "doomstat.h"
  #include "templates.h"
  #include "oalsound.h"
  #include "c_cvars.h"
  #include "c_dispatch.h"
  #include "i_system.h"
  #include "v_text.h"
  #include "gi.h"
  #include "actor.h"
  #include "r_state.h"
  #include "w_wad.h"
  #include "i_module.h"
  #include "i_music.h"
  #include "i_musicinterns.h"
  #include "cmdlib.h"
  
  FModule OpenALModule{"OpenAL"};
  
  #include "oalload.h"
  
  CVAR (String, snd_aldevice, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
  CVAR (Bool, snd_efx, true, CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
  CVAR (String, snd_alresampler, "Default", CVAR_ARCHIVE|CVAR_GLOBALCONFIG)
  
  #ifdef _WIN32
  #define OPENALLIB "openal32.dll"
  #elif defined(__APPLE__)
  #define OPENALLIB "OpenAL.framework/OpenAL"
  #elif defined(__OpenBSD__)
  #define OPENALLIB "libopenal.so"
  #else
  #define OPENALLIB "libopenal.so.1"
  #endif
  
  bool IsOpenALPresent()
  {
  #ifdef NO_OPENAL
  	return false;
  #elif !defined DYN_OPENAL
  	return true;
  #else
  	static bool cached_result = false;
  	static bool done = false;
  
  	if (!done)
  	{
  		done = true;
  		cached_result = OpenALModule.Load({NicePath("$PROGDIR/" OPENALLIB), OPENALLIB});
  	}
  	return cached_result;
  #endif
  }
  
  
  
  void I_BuildALDeviceList(FOptionValues *opt)
  {
  	opt->mValues.Resize(1);
  	opt->mValues[0].TextValue = "Default";
  	opt->mValues[0].Text = "Default";
  
  #ifndef NO_OPENAL
  	if (IsOpenALPresent())
  	{
  		const ALCchar *names = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ?
  			alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) :
  			alcGetString(NULL, ALC_DEVICE_SPECIFIER));
  		if (!names)
  			Printf("Failed to get device list: %s\n", alcGetString(NULL, alcGetError(NULL)));
  		else while (*names)
  		{
  			unsigned int i = opt->mValues.Reserve(1);
  			opt->mValues[i].TextValue = names;
  			opt->mValues[i].Text = names;
  
  			names += strlen(names) + 1;
  		}
  	}
  #endif
  }
  
  void I_BuildALResamplersList(FOptionValues *opt)
  {
  	opt->mValues.Resize(1);
  	opt->mValues[0].TextValue = "Default";
  	opt->mValues[0].Text = "Default";
  
  #ifndef NO_OPENAL
  	if (!IsOpenALPresent())
  		return;
  	if (!alcGetCurrentContext() || !alIsExtensionPresent("AL_SOFT_source_resampler"))
  		return;
  
  	LPALGETSTRINGISOFT alGetStringiSOFT = reinterpret_cast<LPALGETSTRINGISOFT>(alGetProcAddress("alGetStringiSOFT"));
  	ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
  
  	unsigned int idx = opt->mValues.Reserve(num_resamplers);
  	for(ALint i = 0;i < num_resamplers;++i)
  	{
  		const ALchar *name = alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i);
  		opt->mValues[idx].TextValue = name;
  		opt->mValues[idx].Text = name;
  		++idx;
  	}
  #endif
  }
  
  
  ReverbContainer *ForcedEnvironment;
  
  
  #ifndef NO_OPENAL
  
  
  EXTERN_CVAR (Int, snd_channels)
  EXTERN_CVAR (Int, snd_samplerate)
  EXTERN_CVAR (Bool, snd_waterreverb)
  EXTERN_CVAR (Bool, snd_pitched)
  EXTERN_CVAR (Int, snd_hrtf)
  
  
  #define MAKE_PTRID(x)  ((void*)(uintptr_t)(x))
  #define GET_PTRID(x)  ((uint32_t)(uintptr_t)(x))
  
  
  static ALenum checkALError(const char *fn, unsigned int ln)
  {
  	ALenum err = alGetError();
  	if(err != AL_NO_ERROR)
  	{
  		if(strchr(fn, '/'))
  			fn = strrchr(fn, '/')+1;
  		else if(strchr(fn, '\\'))
  			fn = strrchr(fn, '\\')+1;
  		Printf(">>>>>>>>>>>> Received AL error %s (%#x), %s:%u\n", alGetString(err), err, fn, ln);
  	}
  	return err;
  }
  #define getALError() checkALError(__FILE__, __LINE__)
  
  static ALCenum checkALCError(ALCdevice *device, const char *fn, unsigned int ln)
  {
  	ALCenum err = alcGetError(device);
  	if(err != ALC_NO_ERROR)
  	{
  		if(strchr(fn, '/'))
  			fn = strrchr(fn, '/')+1;
  		else if(strchr(fn, '\\'))
  			fn = strrchr(fn, '\\')+1;
  		Printf(">>>>>>>>>>>> Received ALC error %s (%#x), %s:%u\n", alcGetString(device, err), err, fn, ln);
  	}
  	return err;
  }
  #define getALCError(d) checkALCError((d), __FILE__, __LINE__)
  
  
  // Fallback methods for when AL_SOFT_deferred_updates isn't available. In most
  // cases these don't actually do anything, except on some Creative drivers
  // where they act as appropriate fallbacks.
  static ALvoid AL_APIENTRY _wrap_DeferUpdatesSOFT(void)
  {
  	alcSuspendContext(alcGetCurrentContext());
  }
  
  static ALvoid AL_APIENTRY _wrap_ProcessUpdatesSOFT(void)
  {
  	alcProcessContext(alcGetCurrentContext());
  }
  
  
  class OpenALSoundStream : public SoundStream
  {
  	OpenALSoundRenderer *Renderer;
  
  	SoundStreamCallback Callback;
  	void *UserData;
  
  	TArray<ALubyte> Data;
  
  	ALsizei SampleRate;
  	ALenum Format;
  	ALsizei FrameSize;
  
  	static const int BufferCount = 4;
  	ALuint Buffers[BufferCount];
  	ALuint Source;
  
  	std::atomic<bool> Playing;
  	bool Looping;
  	ALfloat Volume;
  
  
  	FileReader *Reader;
  	SoundDecoder *Decoder;
  	static bool DecoderCallback(SoundStream *_sstream, void *ptr, int length, void *user)
  	{
  		OpenALSoundStream *self = static_cast<OpenALSoundStream*>(_sstream);
  		if(length < 0) return false;
  
  		size_t got = self->Decoder->read((char*)ptr, length);
  		if(got < (unsigned int)length)
  		{
  			if(!self->Looping || !self->Decoder->seek(0, false, true))
  				return false;
  			got += self->Decoder->read((char*)ptr+got, length-got);
  		}
  
  		return (got == (unsigned int)length);
  	}
  
  
  	bool SetupSource()
  	{
  		/* Get a source, killing the farthest, lowest-priority sound if needed */
  		if(Renderer->FreeSfx.Size() == 0)
  		{
  			FSoundChan *lowest = Renderer->FindLowestChannel();
  			if(lowest) Renderer->ForceStopChannel(lowest);
  
  			if(Renderer->FreeSfx.Size() == 0)
  				return false;
  		}
  		Renderer->FreeSfx.Pop(Source);
  
  		/* Set the default properties for localized playback */
  		alSource3f(Source, AL_DIRECTION, 0.f, 0.f, 0.f);
  		alSource3f(Source, AL_VELOCITY, 0.f, 0.f, 0.f);
  		alSource3f(Source, AL_POSITION, 0.f, 0.f, 0.f);
  		alSourcef(Source, AL_MAX_GAIN, 1.f);
  		alSourcef(Source, AL_GAIN, 1.f);
  		alSourcef(Source, AL_PITCH, 1.f);
  		alSourcef(Source, AL_DOPPLER_FACTOR, 0.f);
  		alSourcef(Source, AL_ROLLOFF_FACTOR, 0.f);
  		alSourcef(Source, AL_SEC_OFFSET, 0.f);
  		alSourcei(Source, AL_SOURCE_RELATIVE, AL_TRUE);
  		alSourcei(Source, AL_LOOPING, AL_FALSE);
  		if(Renderer->EnvSlot)
  		{
  			alSourcef(Source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
  			alSourcef(Source, AL_AIR_ABSORPTION_FACTOR, 0.f);
  			alSourcei(Source, AL_DIRECT_FILTER, AL_FILTER_NULL);
  			alSource3i(Source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
  		}
  		if(Renderer->AL.EXT_SOURCE_RADIUS)
  			alSourcef(Source, AL_SOURCE_RADIUS, 0.f);
  		if(Renderer->AL.SOFT_source_spatialize)
  			alSourcei(Source, AL_SOURCE_SPATIALIZE_SOFT, AL_AUTO_SOFT);
  
  		alGenBuffers(BufferCount, Buffers);
  		return (getALError() == AL_NO_ERROR);
  	}
  
  public:
  	OpenALSoundStream(OpenALSoundRenderer *renderer)
  	  : Renderer(renderer), Source(0), Playing(false), Looping(false), Volume(1.0f), Reader(NULL), Decoder(NULL)
  	{
  		memset(Buffers, 0, sizeof(Buffers));
  		Renderer->AddStream(this);
  	}
  
  	virtual ~OpenALSoundStream()
  	{
  		Renderer->RemoveStream(this);
  
  		if(Source)
  		{
  			alSourceRewind(Source);
  			alSourcei(Source, AL_BUFFER, 0);
  
  			Renderer->FreeSfx.Push(Source);
  			Source = 0;
  		}
  
  		if(Buffers[0])
  		{
  			alDeleteBuffers(BufferCount, &Buffers[0]);
  			memset(Buffers, 0, sizeof(Buffers));
  		}
  		getALError();
  
  		delete Decoder;
  		delete Reader;
  	}
  
  
  	virtual bool Play(bool loop, float vol)
  	{
  		SetVolume(vol);
  
  		if(Playing.load())
  			return true;
  
  		/* Clear the buffer queue, then fill and queue each buffer */
  		alSourcei(Source, AL_BUFFER, 0);
  		for(int i = 0;i < BufferCount;i++)
  		{
  			if(!Callback(this, &Data[0], Data.Size(), UserData))
  			{
  				if(i == 0)
  					return false;
  				break;
  			}
  
  			alBufferData(Buffers[i], Format, &Data[0], Data.Size(), SampleRate);
  			alSourceQueueBuffers(Source, 1, &Buffers[i]);
  		}
  		if(getALError() != AL_NO_ERROR)
  			return false;
  
  		alSourcePlay(Source);
  		if(getALError() != AL_NO_ERROR)
  			return false;
  
  		Playing.store(true);
  		return true;
  	}
  
  	virtual void Stop()
  	{
  		if(!Playing.load())
  			return;
  
  		std::unique_lock<std::mutex> lock(Renderer->StreamLock);
  		alSourceStop(Source);
  		alSourcei(Source, AL_BUFFER, 0);
  		getALError();
  
  		Playing.store(false);
  	}
  
  	virtual void SetVolume(float vol)
  	{
  		Volume = vol;
  		UpdateVolume();
  	}
  
  	void UpdateVolume()
  	{
  		alSourcef(Source, AL_GAIN, Renderer->MusicVolume*Volume);
  		getALError();
  	}
  
  	virtual bool SetPaused(bool pause)
  	{
  		if(pause)
  			alSourcePause(Source);
  		else
  			alSourcePlay(Source);
  		return (getALError()==AL_NO_ERROR);
  	}
  
  	virtual bool SetPosition(unsigned int ms_pos)
  	{
  		std::unique_lock<std::mutex> lock(Renderer->StreamLock);
  		if(!Decoder->seek(ms_pos, true, false))
  			return false;
  
  		if(!Playing.load())
  			return true;
  		// Stop the source so that all buffers become processed, which will
  		// allow the next update to restart the source queue with the new
  		// position.
  		alSourceStop(Source);
  		getALError();
  		lock.unlock();
  		Renderer->StreamWake.notify_all();
  		return true;
  	}
  
  	virtual unsigned int GetPosition()
  	{
  		std::unique_lock<std::mutex> lock(Renderer->StreamLock);
  		ALint offset, queued, state;
  		alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset);
  		alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
  		alGetSourcei(Source, AL_SOURCE_STATE, &state);
  		if(getALError() != AL_NO_ERROR)
  			return 0;
  
  		size_t pos = Decoder->getSampleOffset();
  		lock.unlock();
  
  		if(state != AL_STOPPED)
  		{
  			size_t rem = queued*(Data.Size()/FrameSize) - offset;
  			if(pos > rem) pos -= rem;
  			else pos = 0;
  		}
  		return (unsigned int)(pos * 1000.0 / SampleRate);
  	}
  
  	virtual bool IsEnded()
  	{
  		return !Playing.load();
  			}
  
  	virtual FString GetStats()
  		{
  		FString stats;
  		size_t pos = 0, len = 0;
  		ALfloat volume;
  		ALint offset;
  		ALint processed;
  		ALint queued;
  		ALint state;
  		ALenum err;
  
  		std::unique_lock<std::mutex> lock(Renderer->StreamLock);
  		alGetSourcef(Source, AL_GAIN, &volume);
  		alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset);
  		alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed);
  		alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
  		alGetSourcei(Source, AL_SOURCE_STATE, &state);
  		if((err=alGetError()) != AL_NO_ERROR)
  		{
  			lock.unlock();
  			stats = "Error getting stats: ";
  			stats += alGetString(err);
  			return stats;
  		}
  
  		if (Decoder != nullptr)
  		{
  			pos = Decoder->getSampleOffset();
  			len = Decoder->getSampleLength();
  		}
  		lock.unlock();
  
  		stats = (state == AL_INITIAL) ? "Buffering" : (state == AL_STOPPED) ? "Underrun" :
  				(state == AL_PLAYING || state == AL_PAUSED) ? "Ready" : "Unknown state";
  
  		if (Decoder != nullptr)
  		{
  			if (state == AL_STOPPED)
  				offset = BufferCount * (Data.Size() / FrameSize);
  			else
  			{
  				size_t rem = queued*(Data.Size() / FrameSize) - offset;
  				if (pos > rem) pos -= rem;
  				else if (len > 0) pos += len - rem;
  				else pos = 0;
  			}
  			pos = (size_t)(pos * 1000.0 / SampleRate);
  			len = (size_t)(len * 1000.0 / SampleRate);
  			stats.AppendFormat(",%3u%% buffered", 100 - 100 * offset / (BufferCount*(Data.Size() / FrameSize)));
  			stats.AppendFormat(", %zu.%03zu", pos / 1000, pos % 1000);
  			if (len > 0)
  				stats.AppendFormat(" / %zu.%03zu", len / 1000, len % 1000);
  		}
  		if(state == AL_PAUSED)
  			stats += ", paused";
  		if(state == AL_PLAYING)
  			stats += ", playing";
  		stats.AppendFormat(", %uHz", SampleRate);
  		if(!Playing)
  			stats += " XX";
  		return stats;
  	}
  
  	bool Process()
  	{
  		if(!Playing.load())
  			return false;
  
  		ALint state, processed;
  		alGetSourcei(Source, AL_SOURCE_STATE, &state);
  		alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed);
  		if(getALError() != AL_NO_ERROR)
  		{
  			Playing.store(false);
  			return false;
  		}
  
  		// For each processed buffer in the queue...
  		while(processed > 0)
  		{
  			ALuint bufid;
  
  			// Unqueue the oldest buffer, fill it with more data, and queue it
  			// on the end
  			alSourceUnqueueBuffers(Source, 1, &bufid);
  			processed--;
  
  			if(Callback(this, &Data[0], Data.Size(), UserData))
  			{
  				alBufferData(bufid, Format, &Data[0], Data.Size(), SampleRate);
  				alSourceQueueBuffers(Source, 1, &bufid);
  			}
  		}
  
  		// If the source is not playing or paused, and there are buffers queued,
  		// then there was an underrun. Restart the source.
  		bool ok = (getALError()==AL_NO_ERROR);
  		if(ok && state != AL_PLAYING && state != AL_PAUSED)
  		{
  			ALint queued = 0;
  			alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued);
  
  			ok = (getALError() == AL_NO_ERROR) && (queued > 0);
  			if(ok)
  			{
  				alSourcePlay(Source);
  				ok = (getALError()==AL_NO_ERROR);
  			}
  		}
  
  		Playing.store(ok);
  		return ok;
  	}
  
  	bool Init(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
  	{
  		if(!SetupSource())
  			return false;
  
  		Callback = callback;
  		UserData = userdata;
  		SampleRate = samplerate;
  
  		Format = AL_NONE;
  		if((flags&Bits8)) /* Signed or unsigned? We assume unsigned 8-bit... */
  		{
  			if((flags&Mono)) Format = AL_FORMAT_MONO8;
  			else Format = AL_FORMAT_STEREO8;
  		}
  		else if((flags&Float))
  		{
  			if(alIsExtensionPresent("AL_EXT_FLOAT32"))
  			{
  				if((flags&Mono)) Format = AL_FORMAT_MONO_FLOAT32;
  				else Format = AL_FORMAT_STEREO_FLOAT32;
  			}
  		}
  		else if((flags&Bits32))
  		{
  		}
  		else
  		{
  			if((flags&Mono)) Format = AL_FORMAT_MONO16;
  			else Format = AL_FORMAT_STEREO16;
  		}
  
  		if(Format == AL_NONE)
  		{
  			Printf("Unsupported format: 0x%x\n", flags);
  			return false;
  		}
  
  		FrameSize = 1;
  		if((flags&Bits8))
  			FrameSize *= 1;
  		else if((flags&(Bits32|Float)))
  			FrameSize *= 4;
  		else
  			FrameSize *= 2;
  
  		if((flags&Mono))
  			FrameSize *= 1;
  		else
  			FrameSize *= 2;
  
  		buffbytes += FrameSize-1;
  		buffbytes -= buffbytes%FrameSize;
  		Data.Resize(buffbytes);
  
  		return true;
  	}
  
  	bool Init(FileReader *reader, bool loop)
  	{
  		if(!SetupSource())
  		{
  			delete reader;
  			return false;
  		}
  
  		if(Decoder) delete Decoder;
  		if(Reader) delete Reader;
  		Reader = reader;
  		Decoder = Renderer->CreateDecoder(Reader);
  		if(!Decoder) return false;
  
  		Callback = DecoderCallback;
  		UserData = NULL;
  		Format = AL_NONE;
  		FrameSize = 1;
  
  		ChannelConfig chans;
  		SampleType type;
  		int srate;
  
  		Decoder->getInfo(&srate, &chans, &type);
  		if(chans == ChannelConfig_Mono)
  		{
  			if(type == SampleType_UInt8) Format = AL_FORMAT_MONO8;
  			if(type == SampleType_Int16) Format = AL_FORMAT_MONO16;
  			FrameSize *= 1;
  		}
  		if(chans == ChannelConfig_Stereo)
  		{
  			if(type == SampleType_UInt8) Format = AL_FORMAT_STEREO8;
  			if(type == SampleType_Int16) Format = AL_FORMAT_STEREO16;
  			FrameSize *= 2;
  		}
  		if(type == SampleType_UInt8) FrameSize *= 1;
  		if(type == SampleType_Int16) FrameSize *= 2;
  
  		if(Format == AL_NONE)
  		{
  			Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
  				   GetSampleTypeName(type));
  			return false;
  		}
  		SampleRate = srate;
  		Looping = loop;
  
  		Data.Resize((SampleRate / 5) * FrameSize);
  
  		return true;
  	}
  };
  
  
  #define AREA_SOUND_RADIUS  (32.f)
  
  #define PITCH_MULT (0.7937005f) /* Approx. 4 semitones lower; what Nash suggested */
  
  #define PITCH(pitch) (snd_pitched ? (pitch)/128.f : 1.f)
  
  static size_t GetChannelCount(ChannelConfig chans)
  {
  	switch(chans)
  	{
  		case ChannelConfig_Mono: return 1;
  		case ChannelConfig_Stereo: return 2;
  	}
  	return 0;
  }
  
  static float GetRolloff(const FRolloffInfo *rolloff, float distance)
  {
  	if(distance <= rolloff->MinDistance)
  		return 1.f;
  	// Logarithmic rolloff has no max distance where it goes silent.
  	if(rolloff->RolloffType == ROLLOFF_Log)
  		return rolloff->MinDistance /
  			   (rolloff->MinDistance + rolloff->RolloffFactor*(distance-rolloff->MinDistance));
  	if(distance >= rolloff->MaxDistance)
  		return 0.f;
  
  	float volume = (rolloff->MaxDistance - distance) / (rolloff->MaxDistance - rolloff->MinDistance);
  	if(rolloff->RolloffType == ROLLOFF_Linear)
  		return volume;
  
  	if(rolloff->RolloffType == ROLLOFF_Custom && S_SoundCurve != NULL)
  		return S_SoundCurve[int(S_SoundCurveSize * (1.f - volume))] / 127.f;
  	return (powf(10.f, volume) - 1.f) / 9.f;
  }
  
  ALCdevice *OpenALSoundRenderer::InitDevice()
  {
  	ALCdevice *device = NULL;
  	if (IsOpenALPresent())
  	{
  		if(strcmp(snd_aldevice, "Default") != 0)
  		{
  			device = alcOpenDevice(*snd_aldevice);
  			if(!device)
  				Printf(TEXTCOLOR_BLUE" Failed to open device " TEXTCOLOR_BOLD"%s" TEXTCOLOR_BLUE". Trying default.\n", *snd_aldevice);
  		}
  
  		if(!device)
  		{
  			device = alcOpenDevice(NULL);
  			if(!device)
  			{
  				Printf(TEXTCOLOR_RED" Could not open audio device\n");
  			}
  		}
  	}
  	else
  	{
  		Printf(TEXTCOLOR_ORANGE"Failed to load openal32.dll\n");
  	}
  	return device;
  }
  
  
  template<typename T>
  static void LoadALFunc(const char *name, T *x)
  {
  	*x = reinterpret_cast<T>(alGetProcAddress(name));
  }
  
  template<typename T>
  static void LoadALCFunc(ALCdevice *device, const char *name, T *x)
  {
  	*x = reinterpret_cast<T>(alcGetProcAddress(device, name));
  }
  
  #define LOAD_FUNC(x)  (LoadALFunc(#x, &x))
  #define LOAD_DEV_FUNC(d, x)  (LoadALCFunc(d, #x, &x))
  OpenALSoundRenderer::OpenALSoundRenderer()
  	: QuitThread(false), Device(NULL), Context(NULL), SFXPaused(0), PrevEnvironment(NULL), EnvSlot(0)
  {
  	EnvFilters[0] = EnvFilters[1] = 0;
  
  	Printf("I_InitSound: Initializing OpenAL\n");
  
  	Device = InitDevice();
  	if (Device == NULL) return;
  
  	ALC.EXT_EFX = !!alcIsExtensionPresent(Device, "ALC_EXT_EFX");
  	ALC.EXT_disconnect = !!alcIsExtensionPresent(Device, "ALC_EXT_disconnect");
  	ALC.SOFT_HRTF = !!alcIsExtensionPresent(Device, "ALC_SOFT_HRTF");
  	ALC.SOFT_pause_device = !!alcIsExtensionPresent(Device, "ALC_SOFT_pause_device");
  
  	const ALCchar *current = NULL;
  	if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT"))
  		current = alcGetString(Device, ALC_ALL_DEVICES_SPECIFIER);
  	if(alcGetError(Device) != ALC_NO_ERROR || !current)
  		current = alcGetString(Device, ALC_DEVICE_SPECIFIER);
  	Printf("  Opened device " TEXTCOLOR_ORANGE"%s\n", current);
  
  	ALCint major=0, minor=0;
  	alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major);
  	alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor);
  	DPrintf(DMSG_SPAMMY, "	ALC Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
  	DPrintf(DMSG_SPAMMY, "	ALC Extensions: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS));
  
  	TArray<ALCint> attribs;
  	if(*snd_samplerate > 0)
  	{
  		attribs.Push(ALC_FREQUENCY);
  		attribs.Push(*snd_samplerate);
  	}
  	// Make sure one source is capable of stereo output with the rest doing
  	// mono, without running out of voices
  	attribs.Push(ALC_MONO_SOURCES);
  	attribs.Push(MAX<ALCint>(*snd_channels, 2) - 1);
  	attribs.Push(ALC_STEREO_SOURCES);
  	attribs.Push(1);
  	if(ALC.SOFT_HRTF)
  	{
  		attribs.Push(ALC_HRTF_SOFT);
  		if(*snd_hrtf == 0)
  			attribs.Push(ALC_FALSE);
  		else if(*snd_hrtf > 0)
  			attribs.Push(ALC_TRUE);
  		else
  			attribs.Push(ALC_DONT_CARE_SOFT);
  	}
  	// Other attribs..?
  	attribs.Push(0);
  
  	Context = alcCreateContext(Device, &attribs[0]);
  	if(!Context || alcMakeContextCurrent(Context) == ALC_FALSE)
  	{
  		Printf(TEXTCOLOR_RED"  Failed to setup context: %s\n", alcGetString(Device, alcGetError(Device)));
  		if(Context)
  			alcDestroyContext(Context);
  		Context = NULL;
  		alcCloseDevice(Device);
  		Device = NULL;
  		return;
  	}
  	attribs.Clear();
  
  	DPrintf(DMSG_SPAMMY, "	Vendor: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR));
  	DPrintf(DMSG_SPAMMY, "	Renderer: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER));
  	DPrintf(DMSG_SPAMMY, "	Version: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION));
  	DPrintf(DMSG_SPAMMY, "	Extensions: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS));
  
  	AL.EXT_source_distance_model = !!alIsExtensionPresent("AL_EXT_source_distance_model");
  	AL.EXT_SOURCE_RADIUS = !!alIsExtensionPresent("AL_EXT_SOURCE_RADIUS");
  	AL.SOFT_deferred_updates = !!alIsExtensionPresent("AL_SOFT_deferred_updates");
  	AL.SOFT_loop_points = !!alIsExtensionPresent("AL_SOFT_loop_points");
  	AL.SOFT_source_resampler = !!alIsExtensionPresent("AL_SOFT_source_resampler");
  	AL.SOFT_source_spatialize = !!alIsExtensionPresent("AL_SOFT_source_spatialize");
  
  	// Speed of sound is in units per second. Presuming we want to simulate a
  	// typical speed of sound of 343.3 meters per second, multiply it by the
  	// units per meter scale (32?), and set the meters per unit to the scale's
  	// reciprocal. It's important to set these correctly for both doppler
  	// effects and reverb.
  	alSpeedOfSound(343.3f * 32.0f);
  	if(ALC.EXT_EFX)
  		alListenerf(AL_METERS_PER_UNIT, 1.0f/32.0f);
  
  	alDistanceModel(AL_INVERSE_DISTANCE);
  	if(AL.EXT_source_distance_model)
  		alEnable(AL_SOURCE_DISTANCE_MODEL);
  
  	if(AL.SOFT_deferred_updates)
  	{
  		LOAD_FUNC(alDeferUpdatesSOFT);
  		LOAD_FUNC(alProcessUpdatesSOFT);
  	}
  	else
  	{
  		alDeferUpdatesSOFT = _wrap_DeferUpdatesSOFT;
  		alProcessUpdatesSOFT = _wrap_ProcessUpdatesSOFT;
  	}
  
  	if(AL.SOFT_source_resampler)
  		LOAD_FUNC(alGetStringiSOFT);
  
  	if(ALC.SOFT_pause_device)
  	{
  		LOAD_DEV_FUNC(Device, alcDevicePauseSOFT);
  		LOAD_DEV_FUNC(Device, alcDeviceResumeSOFT);
  	}
  
  	ALenum err = getALError();
  	if(err != AL_NO_ERROR)
  	{
  		alcMakeContextCurrent(NULL);
  		alcDestroyContext(Context);
  		Context = NULL;
  		alcCloseDevice(Device);
  		Device = NULL;
  		return;
  	}
  
  	ALCint refresh=0;
  	alcGetIntegerv(Device, ALC_REFRESH, 1, &refresh);
  	if(refresh > 0)
  	{
  		// Round up instead of down
  		UpdateTimeMS = (1000+refresh-1) / refresh;
  	}
  
  	ALCint numMono=0, numStereo=0;
  	alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &numMono);
  	alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &numStereo);
  
  	// OpenAL specification doesn't require alcGetIntegerv() to return
  	// meaningful values for ALC_MONO_SOURCES and ALC_MONO_SOURCES.
  	// At least Apple's OpenAL implementation returns zeroes,
  	// although it can generate reasonable number of sources.
  
  	const int numChannels = MAX<int>(*snd_channels, 2);
  	int numSources = numMono + numStereo;
  
  	if (0 == numSources)
  	{
  		numSources = numChannels;
  	}
  
  	Sources.Resize(MIN<int>(numChannels, numSources));
  	for(unsigned i = 0;i < Sources.Size();i++)
  	{
  		alGenSources(1, &Sources[i]);
  		if(getALError() != AL_NO_ERROR)
  		{
  			Sources.Resize(i);
  			Sources.ShrinkToFit();
  			break;
  		}
  	}
  	if(Sources.Size() == 0)
  	{
  		Printf(TEXTCOLOR_RED" Error: could not generate any sound sources!\n");
  		alcMakeContextCurrent(NULL);
  		alcDestroyContext(Context);
  		Context = NULL;
  		alcCloseDevice(Device);
  		Device = NULL;
  		return;
  	}
  	FreeSfx = Sources;
  	DPrintf(DMSG_NOTIFY, "	Allocated " TEXTCOLOR_BLUE"%u" TEXTCOLOR_NORMAL" sources\n", Sources.Size());
  
  	WasInWater = false;
  	if(*snd_efx && ALC.EXT_EFX)
  	{
  		// EFX function pointers
  		LOAD_FUNC(alGenEffects);
  		LOAD_FUNC(alDeleteEffects);
  		LOAD_FUNC(alIsEffect);
  		LOAD_FUNC(alEffecti);
  		LOAD_FUNC(alEffectiv);
  		LOAD_FUNC(alEffectf);
  		LOAD_FUNC(alEffectfv);
  		LOAD_FUNC(alGetEffecti);
  		LOAD_FUNC(alGetEffectiv);
  		LOAD_FUNC(alGetEffectf);
  		LOAD_FUNC(alGetEffectfv);
  
  		LOAD_FUNC(alGenFilters);
  		LOAD_FUNC(alDeleteFilters);
  		LOAD_FUNC(alIsFilter);
  		LOAD_FUNC(alFilteri);
  		LOAD_FUNC(alFilteriv);
  		LOAD_FUNC(alFilterf);
  		LOAD_FUNC(alFilterfv);
  		LOAD_FUNC(alGetFilteri);
  		LOAD_FUNC(alGetFilteriv);
  		LOAD_FUNC(alGetFilterf);
  		LOAD_FUNC(alGetFilterfv);
  
  		LOAD_FUNC(alGenAuxiliaryEffectSlots);
  		LOAD_FUNC(alDeleteAuxiliaryEffectSlots);
  		LOAD_FUNC(alIsAuxiliaryEffectSlot);
  		LOAD_FUNC(alAuxiliaryEffectSloti);
  		LOAD_FUNC(alAuxiliaryEffectSlotiv);
  		LOAD_FUNC(alAuxiliaryEffectSlotf);
  		LOAD_FUNC(alAuxiliaryEffectSlotfv);
  		LOAD_FUNC(alGetAuxiliaryEffectSloti);
  		LOAD_FUNC(alGetAuxiliaryEffectSlotiv);
  		LOAD_FUNC(alGetAuxiliaryEffectSlotf);
  		LOAD_FUNC(alGetAuxiliaryEffectSlotfv);
  		if(getALError() == AL_NO_ERROR)
  		{
  			ALuint envReverb;
  			alGenEffects(1, &envReverb);
  			if(getALError() == AL_NO_ERROR)
  			{
  				alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
  				if(alGetError() == AL_NO_ERROR)
  					DPrintf(DMSG_SPAMMY, "	EAX Reverb found\n");
  				alEffecti(envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
  				if(alGetError() == AL_NO_ERROR)
  					DPrintf(DMSG_SPAMMY, "	Standard Reverb found\n");
  
  				alDeleteEffects(1, &envReverb);
  				getALError();
  			}
  
  			alGenAuxiliaryEffectSlots(1, &EnvSlot);
  			alGenFilters(2, EnvFilters);
  			if(getALError() == AL_NO_ERROR)
  			{
  				alFilteri(EnvFilters[0], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
  				alFilteri(EnvFilters[1], AL_FILTER_TYPE, AL_FILTER_LOWPASS);
  				if(getALError() == AL_NO_ERROR)
  					DPrintf(DMSG_SPAMMY, "	Lowpass found\n");
  				else
  				{
  					alDeleteFilters(2, EnvFilters);
  					EnvFilters[0] = EnvFilters[1] = 0;
  					alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
  					EnvSlot = 0;
  					getALError();
  				}
  			}
  			else
  			{
  				alDeleteFilters(2, EnvFilters);
  				alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
  				EnvFilters[0] = EnvFilters[1] = 0;
  				EnvSlot = 0;
  				getALError();
  			}
  		}
  	}
  
  	if(EnvSlot)
  		Printf("  EFX enabled\n");
  
  	if(AL.SOFT_source_resampler && strcmp(*snd_alresampler, "Default") != 0)
  	{
  		const ALint num_resamplers = alGetInteger(AL_NUM_RESAMPLERS_SOFT);
  		ALint ridx = alGetInteger(AL_DEFAULT_RESAMPLER_SOFT);
  		ALint i;
  
  		for(i = 0;i < num_resamplers;i++)
  		{
  			if(strcmp(alGetStringiSOFT(AL_RESAMPLER_NAME_SOFT, i), *snd_alresampler) == 0)
  			{
  				ridx = i;
  				break;
  			}
  		}
  		if(i == num_resamplers)
  			Printf(TEXTCOLOR_RED" Failed to find resampler " TEXTCOLOR_ORANGE"%s\n", *snd_alresampler);
  		else for(ALint src : Sources)
  			alSourcei(src, AL_SOURCE_RESAMPLER_SOFT, ridx);
  	}
  }
  #undef LOAD_DEV_FUNC
  #undef LOAD_FUNC
  
  OpenALSoundRenderer::~OpenALSoundRenderer()
  {
  	if(!Device)
  		return;
  
  	if(StreamThread.joinable())
  	{
  		std::unique_lock<std::mutex> lock(StreamLock);
  		QuitThread.store(true);
  		lock.unlock();
  		StreamWake.notify_all();
  		StreamThread.join();
  	}
  
  	while(Streams.Size() > 0)
  		delete Streams[0];
  
  	alDeleteSources(Sources.Size(), &Sources[0]);
  	Sources.Clear();
  	FreeSfx.Clear();
  	SfxGroup.Clear();
  	PausableSfx.Clear();
  	ReverbSfx.Clear();
  
  	if(EnvEffects.CountUsed() > 0)
  	{
  		EffectMapIter iter(EnvEffects);
  		EffectMap::Pair *pair;
  		while(iter.NextPair(pair))
  			alDeleteEffects(1, &(pair->Value));
  	}
  	EnvEffects.Clear();
  
  	if(EnvSlot)
  	{
  		alDeleteAuxiliaryEffectSlots(1, &EnvSlot);
  		alDeleteFilters(2, EnvFilters);
  	}
  	EnvSlot = 0;
  	EnvFilters[0] = EnvFilters[1] = 0;
  
  	alcMakeContextCurrent(NULL);
  	alcDestroyContext(Context);
  	Context = NULL;
  	alcCloseDevice(Device);
  	Device = NULL;
  }
  
  void OpenALSoundRenderer::BackgroundProc()
  {
  	std::unique_lock<std::mutex> lock(StreamLock);
  	while(!QuitThread.load())
  	{
  		if(Streams.Size() == 0)
  		{
  			// If there's nothing to play, wait indefinitely.
  			StreamWake.wait(lock);
  		}
  		else
  		{
  			// Else, process all active streams and sleep for 100ms
  			for(size_t i = 0;i < Streams.Size();i++)
  				Streams[i]->Process();
  			StreamWake.wait_for(lock, std::chrono::milliseconds(100));
  		}
  	}
  }
  
  void OpenALSoundRenderer::AddStream(OpenALSoundStream *stream)
  {
  	std::unique_lock<std::mutex> lock(StreamLock);
  	Streams.Push(stream);
  	lock.unlock();
  	// There's a stream to play, make sure the background thread is aware
  	StreamWake.notify_all();
  }
  
  void OpenALSoundRenderer::RemoveStream(OpenALSoundStream *stream)
  {
  	std::unique_lock<std::mutex> lock(StreamLock);
  	unsigned int idx = Streams.Find(stream);
  	if(idx < Streams.Size())
  		Streams.Delete(idx);
  }
  
  void OpenALSoundRenderer::SetSfxVolume(float volume)
  {
  	SfxVolume = volume;
  
  	FSoundChan *schan = Channels;
  	while(schan)
  	{
  		if(schan->SysChannel != NULL)
  		{
  			ALuint source = GET_PTRID(schan->SysChannel);
  			volume = SfxVolume;
  
  			alDeferUpdatesSOFT();
  			alSourcef(source, AL_MAX_GAIN, volume);
  			alSourcef(source, AL_GAIN, volume * schan->Volume);
  		}
  		schan = schan->NextChan;
  	}
  
  	alProcessUpdatesSOFT();
  
  	getALError();
  }
  
  void OpenALSoundRenderer::SetMusicVolume(float volume)
  {
  	MusicVolume = volume;
  	for(uint32_t i = 0;i < Streams.Size();++i)
  		Streams[i]->UpdateVolume();
  }
  
  unsigned int OpenALSoundRenderer::GetMSLength(SoundHandle sfx)
  {
  	if(sfx.data)
  	{
  		ALuint buffer = GET_PTRID(sfx.data);
  		if(alIsBuffer(buffer))
  		{
  			ALint bits, channels, freq, size;
  			alGetBufferi(buffer, AL_BITS, &bits);
  			alGetBufferi(buffer, AL_CHANNELS, &channels);
  			alGetBufferi(buffer, AL_FREQUENCY, &freq);
  			alGetBufferi(buffer, AL_SIZE, &size);
  			if(getALError() == AL_NO_ERROR)
  				return (unsigned int)(size / (channels*bits/8) * 1000. / freq);
  		}
  	}
  	return 0;
  }
  
  unsigned int OpenALSoundRenderer::GetSampleLength(SoundHandle sfx)
  {
  	if(sfx.data)
  	{
  		ALuint buffer = GET_PTRID(sfx.data);
  		ALint bits, channels, size;
  		alGetBufferi(buffer, AL_BITS, &bits);
  		alGetBufferi(buffer, AL_CHANNELS, &channels);
  		alGetBufferi(buffer, AL_SIZE, &size);
  		if(getALError() == AL_NO_ERROR)
  			return (ALsizei)(size / (channels * bits / 8));
  	}
  	return 0;
  }
  
  float OpenALSoundRenderer::GetOutputRate()
  {
  	ALCint rate = 44100; // Default, just in case
  	alcGetIntegerv(Device, ALC_FREQUENCY, 1, &rate);
  	return (float)rate;
  }
  
  
  std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSoundRaw(uint8_t *sfxdata, int length, int frequency, int channels, int bits, int loopstart, int loopend, bool monoize)
  {
  	SoundHandle retval = { NULL };
  
  	if(length == 0) return std::make_pair(retval, true);
  
  	/* Only downmix to mono if we can't spatialize multi-channel sounds. */
  	monoize = monoize && !AL.SOFT_source_spatialize;
  
  	if(bits == -8)
  	{
  		// Simple signed->unsigned conversion
  		for(int i = 0;i < length;i++)
  			sfxdata[i] ^= 0x80;
  		bits = -bits;
  	}
  
  	if(channels > 1 && monoize)
  	{
  		size_t frames = length / channels * 8 / bits;
  		if(bits == 16)
  		{
  			for(size_t i = 0;i < frames;i++)
  			{
  				int sum = 0;
  				for(int c = 0;c < channels;c++)
  					sum += ((short*)sfxdata)[i*channels + c];
  				((short*)sfxdata)[i] = sum / channels;
  			}
  		}
  		else if(bits == 8)
  		{
  			for(size_t i = 0;i < frames;i++)
  			{
  				int sum = 0;
  				for(int c = 0;c < channels;c++)
  					sum += sfxdata[i*channels + c] - 128;
  				sfxdata[i] = (sum / channels) + 128;
  			}
  		}
  		length /= channels;
  		channels = 1;
  	}
  
  	ALenum format = AL_NONE;
  	if(bits == 16)
  	{
  		if(channels == 1) format = AL_FORMAT_MONO16;
  		if(channels == 2) format = AL_FORMAT_STEREO16;
  	}
  	else if(bits == 8)
  	{
  		if(channels == 1) format = AL_FORMAT_MONO8;
  		if(channels == 2) format = AL_FORMAT_STEREO8;
  	}
  
  	if(format == AL_NONE || frequency <= 0)
  	{
  		Printf("Unhandled format: %d bit, %d channel, %d hz\n", bits, channels, frequency);
  		return std::make_pair(retval, true);
  	}
  	length -= length%(channels*bits/8);
  
  	ALenum err;
  	ALuint buffer = 0;
  	alGenBuffers(1, &buffer);
  	alBufferData(buffer, format, sfxdata, length, frequency);
  	if((err=getALError()) != AL_NO_ERROR)
  	{
  		Printf("Failed to buffer data: %s\n", alGetString(err));
  		alDeleteBuffers(1, &buffer);
  		getALError();
  		return std::make_pair(retval, true);
  	}
  
  	if((loopstart > 0 || loopend > 0) && AL.SOFT_loop_points)
  	{
  		if(loopstart < 0)
  			loopstart = 0;
  		if(loopend < loopstart)
  			loopend = length / (channels*bits/8);
  
  		ALint loops[2] = { loopstart, loopend };
  		DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
  		alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
  		getALError();
  	}
  	else if(loopstart > 0 || loopend > 0)
  	{
  		static bool warned = false;
  		if(!warned)
  			Printf(DMSG_WARNING, "Loop points not supported!\n");
  		warned = true;
  	}
  
  	retval.data = MAKE_PTRID(buffer);
  	return std::make_pair(retval, AL.SOFT_source_spatialize || channels==1);
  }
  
  std::pair<SoundHandle,bool> OpenALSoundRenderer::LoadSound(uint8_t *sfxdata, int length, bool monoize, FSoundLoadBuffer *pBuffer)
  {
  	SoundHandle retval = { NULL };
  	MemoryReader reader((const char*)sfxdata, length);
  	ALenum format = AL_NONE;
  	ChannelConfig chans;
  	SampleType type;
  	int srate;
  	uint32_t loop_start = 0, loop_end = ~0u;
  	bool startass = false, endass = false;
  
  	/* Only downmix to mono if we can't spatialize multi-channel sounds. */
  	monoize = monoize && !AL.SOFT_source_spatialize;
  
  	FindLoopTags(&reader, &loop_start, &startass, &loop_end, &endass);
  
  	reader.Seek(0, SEEK_SET);
  	std::unique_ptr<SoundDecoder> decoder(CreateDecoder(&reader));
  	if (!decoder) return std::make_pair(retval, true);
  
  	decoder->getInfo(&srate, &chans, &type);
  	int samplesize = 1;
  	if (chans == ChannelConfig_Mono || monoize)
  	{
  		if (type == SampleType_UInt8) format = AL_FORMAT_MONO8, samplesize = 1;
  		if (type == SampleType_Int16) format = AL_FORMAT_MONO16, samplesize = 2;
  	}
  	else if (chans == ChannelConfig_Stereo)
  	{
  		if (type == SampleType_UInt8) format = AL_FORMAT_STEREO8, samplesize = 2;
  		if (type == SampleType_Int16) format = AL_FORMAT_STEREO16, samplesize = 4;
  	}
  
  	if (format == AL_NONE)
  	{
  		Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
  			GetSampleTypeName(type));
  		return std::make_pair(retval, true);
  	}
  
  	TArray<uint8_t> data = decoder->readAll();
  
  	if(chans != ChannelConfig_Mono && monoize)
  	{
  		size_t chancount = GetChannelCount(chans);
  		size_t frames = data.Size() / chancount /
  						(type == SampleType_Int16 ? 2 : 1);
  		if(type == SampleType_Int16)
  		{
  			short *sfxdata = (short*)&data[0];
  			for(size_t i = 0;i < frames;i++)
  			{
  				int sum = 0;
  				for(size_t c = 0;c < chancount;c++)
  					sum += sfxdata[i*chancount + c];
  				sfxdata[i] = short(sum / chancount);
  			}
  		}
  		else if(type == SampleType_UInt8)
  		{
  			uint8_t *sfxdata = (uint8_t*)&data[0];
  			for(size_t i = 0;i < frames;i++)
  			{
  				int sum = 0;
  				for(size_t c = 0;c < chancount;c++)
  					sum += sfxdata[i*chancount + c] - 128;
  				sfxdata[i] = uint8_t((sum / chancount) + 128);
  			}
  		}
  		data.Resize(unsigned(data.Size()/chancount));
  	}
  
  	ALenum err;
  	ALuint buffer = 0;
  	alGenBuffers(1, &buffer);
  	alBufferData(buffer, format, &data[0], data.Size(), srate);
  	if((err=getALError()) != AL_NO_ERROR)
  	{
  		Printf("Failed to buffer data: %s\n", alGetString(err));
  		alDeleteBuffers(1, &buffer);
  		getALError();
  		return std::make_pair(retval, true);
  	}
  
  	if (!startass) loop_start = Scale(loop_start, srate, 1000);
  	if (!endass && loop_end != ~0u) loop_end = Scale(loop_end, srate, 1000);
  	const uint32_t samples = data.Size() / samplesize;
  	if (loop_start > samples) loop_start = 0;
  	if (loop_end > samples) loop_end = samples;
  
  	if ((loop_start > 0 || loop_end > 0) && loop_end > loop_start && AL.SOFT_loop_points)
  	{
  		ALint loops[2] = { static_cast<ALint>(loop_start), static_cast<ALint>(loop_end) };
  		DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
  		alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
  		// no console messages here, please!
  	}
  
  	retval.data = MAKE_PTRID(buffer);
  	if (pBuffer != nullptr)
  	{
  		pBuffer->mBuffer = std::move(data);
  		pBuffer->loop_start = loop_start;
  		pBuffer->loop_end = loop_end;
  		pBuffer->chans = chans;
  		pBuffer->type = type;
  		pBuffer->srate = srate;
  	}
  	return std::make_pair(retval, AL.SOFT_source_spatialize || chans == ChannelConfig_Mono || monoize);
  }
  
  std::pair<SoundHandle, bool> OpenALSoundRenderer::LoadSoundBuffered(FSoundLoadBuffer *pBuffer, bool monoize)
  {
  	SoundHandle retval = { NULL };
  	ALenum format = AL_NONE;
  	int srate = pBuffer->srate;
  	auto type = pBuffer->type;
  	auto chans = pBuffer->chans;
  	uint32_t loop_start = pBuffer->loop_start, loop_end = pBuffer->loop_end;
  
  	/* Only downmix to mono if we can't spatialize multi-channel sounds. */
  	monoize = monoize && !AL.SOFT_source_spatialize;
  
  	if (chans == ChannelConfig_Mono || monoize)
  	{
  		if (type == SampleType_UInt8) format = AL_FORMAT_MONO8;
  		if (type == SampleType_Int16) format = AL_FORMAT_MONO16;
  	}
  	else if (chans == ChannelConfig_Stereo)
  	{
  		if (type == SampleType_UInt8) format = AL_FORMAT_STEREO8;
  		if (type == SampleType_Int16) format = AL_FORMAT_STEREO16;
  	}
  
  	if (format == AL_NONE)
  	{
  		Printf("Unsupported audio format: %s, %s\n", GetChannelConfigName(chans),
  			GetSampleTypeName(type));
  		return std::make_pair(retval, true);
  	}
  
  	TArray<uint8_t> &data = pBuffer->mBuffer;
  
  	if (pBuffer->chans == ChannelConfig_Stereo && monoize)
  	{
  		size_t chancount = GetChannelCount(chans);
  		size_t frames = data.Size() / chancount /
  			(type == SampleType_Int16 ? 2 : 1);
  		if (type == SampleType_Int16)
  		{
  			short *sfxdata = (short*)&data[0];
  			for (size_t i = 0; i < frames; i++)
  			{
  				int sum = 0;
  				for (size_t c = 0; c < chancount; c++)
  					sum += sfxdata[i*chancount + c];
  				sfxdata[i] = short(sum / chancount);
  			}
  		}
  		else if (type == SampleType_UInt8)
  		{
  			uint8_t *sfxdata = (uint8_t*)&data[0];
  			for (size_t i = 0; i < frames; i++)
  			{
  				int sum = 0;
  				for (size_t c = 0; c < chancount; c++)
  					sum += sfxdata[i*chancount + c] - 128;
  				sfxdata[i] = uint8_t((sum / chancount) + 128);
  			}
  		}
  		data.Resize(unsigned(data.Size() / chancount));
  	}
  
  	ALenum err;
  	ALuint buffer = 0;
  	alGenBuffers(1, &buffer);
  	alBufferData(buffer, format, &data[0], data.Size(), srate);
  	if ((err = getALError()) != AL_NO_ERROR)
  	{
  		Printf("Failed to buffer data: %s\n", alGetString(err));
  		alDeleteBuffers(1, &buffer);
  		getALError();
  		return std::make_pair(retval, true);
  	}
  
  	// the loop points were already validated by the previous load.
  	if ((loop_start > 0 || loop_end > 0) && loop_end > loop_start && AL.SOFT_loop_points)
  	{
  		ALint loops[2] = { static_cast<ALint>(loop_start), static_cast<ALint>(loop_end) };
  		DPrintf(DMSG_NOTIFY, "Setting loop points %d -> %d\n", loops[0], loops[1]);
  		alBufferiv(buffer, AL_LOOP_POINTS_SOFT, loops);
  		// no console messages here, please!
  	}
  
  	retval.data = MAKE_PTRID(buffer);
  	return std::make_pair(retval, AL.SOFT_source_spatialize || chans == ChannelConfig_Mono || monoize);
  }
  
  void OpenALSoundRenderer::UnloadSound(SoundHandle sfx)
  {
  	if(!sfx.data)
  		return;
  
  	ALuint buffer = GET_PTRID(sfx.data);
  	FSoundChan *schan = Channels;
  	while(schan)
  	{
  		if(schan->SysChannel)
  		{
  			ALint bufID = 0;
  			alGetSourcei(GET_PTRID(schan->SysChannel), AL_BUFFER, &bufID);
  			if((ALuint)bufID == buffer)
  			{
  				FSoundChan *next = schan->NextChan;
  				ForceStopChannel(schan);
  				schan = next;
  				continue;
  			}
  		}
  		schan = schan->NextChan;
  	}
  
  	// Make sure to kill any currently fading sounds too
  	for(auto iter = FadingSources.begin();iter != FadingSources.end();)
  	{
  		ALint bufID = 0;
  		alGetSourcei(iter->first, AL_BUFFER, &bufID);
  		if(static_cast<ALuint>(bufID) == buffer)
  		{
  			FreeSource(iter->first);
  			iter = FadingSources.erase(iter);
  		}
  		else
  			++iter;
  	}
  
  	alDeleteBuffers(1, &buffer);
  	getALError();
  }
  
  
  SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata)
  {
  	if(StreamThread.get_id() == std::thread::id())
  		StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this);
  	OpenALSoundStream *stream = new OpenALSoundStream(this);
  	if (!stream->Init(callback, buffbytes, flags, samplerate, userdata))
  	{
  		delete stream;
  		return NULL;
  	}
  	return stream;
  }
  
  SoundStream *OpenALSoundRenderer::OpenStream(FileReader *reader, int flags)
  {
  	if(StreamThread.get_id() == std::thread::id())
  		StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this);
  	OpenALSoundStream *stream = new OpenALSoundStream(this);
  	if (!stream->Init(reader, !!(flags&SoundStream::Loop)))
  	{
  		delete stream;
  		return NULL;
  	}
  	return stream;
  }
  
  FISoundChannel *OpenALSoundRenderer::StartSound(SoundHandle sfx, float vol, int pitch, int chanflags, FISoundChannel *reuse_chan)
  {
  	if(FreeSfx.Size() == 0)
  	{
  		FSoundChan *lowest = FindLowestChannel();
  		if(lowest) ForceStopChannel(lowest);
  
  		if(FreeSfx.Size() == 0)
  			return NULL;
  	}
  
  	ALuint buffer = GET_PTRID(sfx.data);
  	ALuint source = FreeSfx.Last();
  	alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
  	alSource3f(source, AL_VELOCITY, 0.f, 0.f, 0.f);
  	alSource3f(source, AL_DIRECTION, 0.f, 0.f, 0.f);
  	alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
  
  	alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP) ? AL_TRUE : AL_FALSE);
  
  	alSourcef(source, AL_REFERENCE_DISTANCE, 1.f);
  	alSourcef(source, AL_MAX_DISTANCE, 1000.f);
  	alSourcef(source, AL_DOPPLER_FACTOR, 0.f);
  	alSourcef(source, AL_ROLLOFF_FACTOR, 0.f);
  	alSourcef(source, AL_MAX_GAIN, SfxVolume);
  	alSourcef(source, AL_GAIN, SfxVolume*vol);
  	if(AL.EXT_SOURCE_RADIUS)
  		alSourcef(source, AL_SOURCE_RADIUS, 0.f);
  	if(AL.SOFT_source_spatialize)
  		alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_AUTO_SOFT);
  
  	if(EnvSlot)
  	{
  		if(!(chanflags&SNDF_NOREVERB))
  		{
  			alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
  			alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
  		}
  		else
  		{
  			alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL);
  			alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
  		}
  		alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
  	}
  	if(WasInWater && !(chanflags&SNDF_NOREVERB))
  		alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT);
  	else
  		alSourcef(source, AL_PITCH, PITCH(pitch));
  
  	if(!reuse_chan || reuse_chan->StartTime.AsOne == 0)
  		alSourcef(source, AL_SEC_OFFSET, 0.f);
  	else
  	{
  		if((chanflags&SNDF_ABSTIME))
  			alSourcei(source, AL_SAMPLE_OFFSET, reuse_chan->StartTime.Lo);
  		else
  		{
  			float offset = std::chrono::duration_cast<std::chrono::duration<float>>(
  				std::chrono::steady_clock::now().time_since_epoch() -
  				std::chrono::steady_clock::time_point::duration(reuse_chan->StartTime.AsOne)
  			).count();
  			if(offset > 0.f) alSourcef(source, AL_SEC_OFFSET, offset);
  		}
  	}
  	if(getALError() != AL_NO_ERROR)
  		return NULL;
  
  	alSourcei(source, AL_BUFFER, buffer);
  	if((chanflags&SNDF_NOPAUSE) || !SFXPaused)
  		alSourcePlay(source);
  	if(getALError() != AL_NO_ERROR)
  	{
  		alSourcei(source, AL_BUFFER, 0);
  		getALError();
  		return NULL;
  	}
  
  	if(!(chanflags&SNDF_NOREVERB))
  		ReverbSfx.Push(source);
  	if(!(chanflags&SNDF_NOPAUSE))
  		PausableSfx.Push(source);
  	SfxGroup.Push(source);
  	FreeSfx.Pop();
  
  	FISoundChannel *chan = reuse_chan;
  	if(!chan) chan = S_GetChannel(MAKE_PTRID(source));
  	else chan->SysChannel = MAKE_PTRID(source);
  
  	chan->Rolloff.RolloffType = ROLLOFF_Log;
  	chan->Rolloff.RolloffFactor = 0.f;
  	chan->Rolloff.MinDistance = 1.f;
  	chan->DistanceSqr = 0.f;
  	chan->ManualRolloff = false;
  
  	return chan;
  }
  
  FISoundChannel *OpenALSoundRenderer::StartSound3D(SoundHandle sfx, SoundListener *listener, float vol,
  	FRolloffInfo *rolloff, float distscale, int pitch, int priority, const FVector3 &pos, const FVector3 &vel,
  	int channum, int chanflags, FISoundChannel *reuse_chan)
  {
  	float dist_sqr = (float)(pos - listener->position).LengthSquared();
  
  	if(FreeSfx.Size() == 0)
  	{
  		FSoundChan *lowest = FindLowestChannel();
  		if(lowest)
  		{
  			if(lowest->Priority < priority || (lowest->Priority == priority &&
  			                                   lowest->DistanceSqr > dist_sqr))
  				ForceStopChannel(lowest);
  		}
  		if(FreeSfx.Size() == 0)
  			return NULL;
  	}
  
  	bool manualRolloff = true;
  	ALuint buffer = GET_PTRID(sfx.data);
  	ALuint source = FreeSfx.Last();
  	if(rolloff->RolloffType == ROLLOFF_Log)
  	{
  		if(AL.EXT_source_distance_model)
  			alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE);
  		alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale);
  		alSourcef(source, AL_MAX_DISTANCE, (1000.f+rolloff->MinDistance)/distscale);
  		alSourcef(source, AL_ROLLOFF_FACTOR, rolloff->RolloffFactor);
  		manualRolloff = false;
  	}
  	else if(rolloff->RolloffType == ROLLOFF_Linear && AL.EXT_source_distance_model)
  	{
  		alSourcei(source, AL_DISTANCE_MODEL, AL_LINEAR_DISTANCE);
  		alSourcef(source, AL_REFERENCE_DISTANCE, rolloff->MinDistance/distscale);
  		alSourcef(source, AL_MAX_DISTANCE, rolloff->MaxDistance/distscale);
  		alSourcef(source, AL_ROLLOFF_FACTOR, 1.f);
  		manualRolloff = false;
  	}
  	if(manualRolloff)
  	{
  		// How manual rolloff works:
  		//
  		// If a sound is using Custom or Doom style rolloff, or Linear style
  		// when AL_EXT_source_distance_model is not supported, we have to play
  		// around a bit to get appropriate distance attenation. What we do is
  		// calculate the attenuation that should be applied, then given an
  		// Inverse Distance rolloff model with OpenAL, reverse the calculation
  		// to get the distance needed for that much attenuation. The Inverse
  		// Distance calculation is:
  		//
  		// Gain = MinDist / (MinDist + RolloffFactor*(Distance - MinDist))
  		//
  		// Thus, the reverse is:
  		//
  		// Distance = (MinDist/Gain - MinDist)/RolloffFactor + MinDist
  		//
  		// This can be simplified by using a MinDist and RolloffFactor of 1,
  		// which makes it:
  		//
  		// Distance = 1.0f/Gain;
  		//
  		// The source position is then set that many units away from the
  		// listener position, and OpenAL takes care of the rest.
  		if(AL.EXT_source_distance_model)
  			alSourcei(source, AL_DISTANCE_MODEL, AL_INVERSE_DISTANCE);
  		alSourcef(source, AL_REFERENCE_DISTANCE, 1.f);
  		alSourcef(source, AL_MAX_DISTANCE, 100000.f);
  		alSourcef(source, AL_ROLLOFF_FACTOR, 1.f);
  
  		FVector3 dir = pos - listener->position;
  		if(dir.DoesNotApproximatelyEqual(FVector3(0.f, 0.f, 0.f)))
  		{
  			float gain = GetRolloff(rolloff, sqrtf(dist_sqr) * distscale);
  			dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f);
  		}
  		if(AL.EXT_SOURCE_RADIUS)
  		{
  			/* Since the OpenAL distance is decoupled from the sound's distance, get the OpenAL
  			 * distance that corresponds to the area radius. */
  			alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ?
  				// Clamp in case the max distance is <= the area radius
  				1.f/MAX<float>(GetRolloff(rolloff, AREA_SOUND_RADIUS), 0.00001f) : 0.f
  			);
  		}
  		else if((chanflags&SNDF_AREA) && dist_sqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
  		{
  			FVector3 amb(0.f, !(dir.Y>=0.f) ? -1.f : 1.f, 0.f);
  			float a = sqrtf(dist_sqr) / AREA_SOUND_RADIUS;
  			dir = amb + (dir-amb)*a;
  		}
  		dir += listener->position;
  
  		if(dist_sqr < (0.0004f*0.0004f))
  		{
  			// Head relative
  			alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
  			alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
  		}
  		else
  		{
  			alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
  			alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
  		}
  	}
  	else
  	{
  		FVector3 dir = pos;
  		if(AL.EXT_SOURCE_RADIUS)
  			alSourcef(source, AL_SOURCE_RADIUS, (chanflags&SNDF_AREA) ? AREA_SOUND_RADIUS : 0.f);
  		else if((chanflags&SNDF_AREA) && dist_sqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
  		{
  			dir -= listener->position;
  	
  			float mindist = rolloff->MinDistance/distscale;
  			FVector3 amb(0.f, !(dir.Y>=0.f) ? -mindist : mindist, 0.f);
  			float a = sqrtf(dist_sqr) / AREA_SOUND_RADIUS;
  			dir = amb + (dir-amb)*a;
  	
  			dir += listener->position;
  			}
  			if(dist_sqr < (0.0004f*0.0004f))
  			{
  				// Head relative
  				alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
  				alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
  			}
  			else
  			{
  				alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
  			alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
  		}
  	}
  	alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]);
  	alSource3f(source, AL_DIRECTION, 0.f, 0.f, 0.f);
  	alSourcef(source, AL_DOPPLER_FACTOR, 0.f);
  
  	alSourcei(source, AL_LOOPING, (chanflags&SNDF_LOOP) ? AL_TRUE : AL_FALSE);
  
  	alSourcef(source, AL_MAX_GAIN, SfxVolume);
  	alSourcef(source, AL_GAIN, SfxVolume*vol);
  	if(AL.SOFT_source_spatialize)
  		alSourcei(source, AL_SOURCE_SPATIALIZE_SOFT, AL_TRUE);
  
  	if(EnvSlot)
  	{
  		if(!(chanflags&SNDF_NOREVERB))
  		{
  			alSourcei(source, AL_DIRECT_FILTER, EnvFilters[0]);
  			alSource3i(source, AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
  		}
  		else
  		{
  			alSourcei(source, AL_DIRECT_FILTER, AL_FILTER_NULL);
  			alSource3i(source, AL_AUXILIARY_SEND_FILTER, 0, 0, AL_FILTER_NULL);
  		}
  		alSourcef(source, AL_ROOM_ROLLOFF_FACTOR, 0.f);
  	}
  	if(WasInWater && !(chanflags&SNDF_NOREVERB))
  		alSourcef(source, AL_PITCH, PITCH(pitch)*PITCH_MULT);
  	else
  		alSourcef(source, AL_PITCH, PITCH(pitch));
  
  	if(!reuse_chan || reuse_chan->StartTime.AsOne == 0)
  		alSourcef(source, AL_SEC_OFFSET, 0.f);
  	else
  	{
  		if((chanflags&SNDF_ABSTIME))
  			alSourcei(source, AL_SAMPLE_OFFSET, reuse_chan->StartTime.Lo);
  		else
  		{
  			float offset = std::chrono::duration_cast<std::chrono::duration<float>>(
  				std::chrono::steady_clock::now().time_since_epoch() -
  				std::chrono::steady_clock::time_point::duration(reuse_chan->StartTime.AsOne)
  			).count();
  			if(offset > 0.f) alSourcef(source, AL_SEC_OFFSET, offset);
  		}
  	}
  	if(getALError() != AL_NO_ERROR)
  		return NULL;
  
  	alSourcei(source, AL_BUFFER, buffer);
  	if((chanflags&SNDF_NOPAUSE) || !SFXPaused)
  		alSourcePlay(source);
  	if(getALError() != AL_NO_ERROR)
  	{
  		alSourcei(source, AL_BUFFER, 0);
  		getALError();
  		return NULL;
  	}
  
  	if(!(chanflags&SNDF_NOREVERB))
  		ReverbSfx.Push(source);
  	if(!(chanflags&SNDF_NOPAUSE))
  		PausableSfx.Push(source);
  	SfxGroup.Push(source);
  	FreeSfx.Pop();
  
  	FISoundChannel *chan = reuse_chan;
  	if(!chan) chan = S_GetChannel(MAKE_PTRID(source));
  	else chan->SysChannel = MAKE_PTRID(source);
  
  	chan->Rolloff = *rolloff;
  	chan->DistanceSqr = dist_sqr;
  	chan->ManualRolloff = manualRolloff;
  
  	return chan;
  }
  
  void OpenALSoundRenderer::ChannelVolume(FISoundChannel *chan, float volume)
  {
  	if(chan == NULL || chan->SysChannel == NULL)
  		return;
  
  	alDeferUpdatesSOFT();
  
  	ALuint source = GET_PTRID(chan->SysChannel);
  	alSourcef(source, AL_GAIN, SfxVolume * volume);
  }
  
  void OpenALSoundRenderer::FreeSource(ALuint source)
  {
  	alSourceRewind(source);
  	alSourcei(source, AL_BUFFER, 0);
  	getALError();
  
  	uint32_t i;
  	if((i=PausableSfx.Find(source)) < PausableSfx.Size())
  		PausableSfx.Delete(i);
  	if((i=ReverbSfx.Find(source)) < ReverbSfx.Size())
  		ReverbSfx.Delete(i);
  	if((i=SfxGroup.Find(source)) < SfxGroup.Size())
  		SfxGroup.Delete(i);
  
  	FreeSfx.Push(source);
  }
  
  void OpenALSoundRenderer::StopChannel(FISoundChannel *chan)
  {
  	if(chan == NULL || chan->SysChannel == NULL)
  		return;
  
  	ALuint source = GET_PTRID(chan->SysChannel);
  	// Release first, so it can be properly marked as evicted if it's being killed
  	S_ChannelEnded(chan);
  
  	ALint state = AL_INITIAL;
  	alGetSourcei(source, AL_SOURCE_STATE, &state);
  	if(state != AL_PLAYING)
  		FreeSource(source);
  	else
  	{
  		// The sound is being killed while playing, so set its gain to 0 and track it
  		// as it fades.
  		alSourcef(source, AL_GAIN, 0.f);
  		getALError();
  
  		FadingSources.insert(std::make_pair(
  			source, std::chrono::steady_clock::now().time_since_epoch().count()
  		));
  	}
  }
  
  void OpenALSoundRenderer::ForceStopChannel(FISoundChannel *chan)
  {
  	ALuint source = GET_PTRID(chan->SysChannel);
  	if(!source) return;
  
  	S_ChannelEnded(chan);
  	FreeSource(source);
  }
  
  
  unsigned int OpenALSoundRenderer::GetPosition(FISoundChannel *chan)
  {
  	if(chan == NULL || chan->SysChannel == NULL)
  		return 0;
  
  	ALint pos;
  	alGetSourcei(GET_PTRID(chan->SysChannel), AL_SAMPLE_OFFSET, &pos);
  	if(getALError() == AL_NO_ERROR)
  		return pos;
  	return 0;
  }
  
  
  void OpenALSoundRenderer::SetSfxPaused(bool paused, int slot)
  {
  	int oldslots = SFXPaused;
  
  	if(paused)
  	{
  		SFXPaused |= 1 << slot;
  		if(oldslots == 0 && PausableSfx.Size() > 0)
  		{
  			alSourcePausev(PausableSfx.Size(), &PausableSfx[0]);
  			getALError();
  			PurgeStoppedSources();
  		}
  	}
  	else
  	{
  		SFXPaused &= ~(1 << slot);
  		if(SFXPaused == 0 && oldslots != 0 && PausableSfx.Size() > 0)
  		{
  			alSourcePlayv(PausableSfx.Size(), &PausableSfx[0]);
  			getALError();
  		}
  	}
  }
  
  void OpenALSoundRenderer::SetInactive(SoundRenderer::EInactiveState state)
  {
  	switch(state)
  	{
  		case SoundRenderer::INACTIVE_Active:
  			alListenerf(AL_GAIN, 1.0f);
  			if(ALC.SOFT_pause_device)
  			{
  				alcDeviceResumeSOFT(Device);
  				getALCError(Device);
  			}
  			break;
  
  		case SoundRenderer::INACTIVE_Complete:
  			if(ALC.SOFT_pause_device)
  			{
  				alcDevicePauseSOFT(Device);
  				getALCError(Device);
  			}
  			/* fall-through */
  		case SoundRenderer::INACTIVE_Mute:
  			alListenerf(AL_GAIN, 0.0f);
  			break;
  	}
  }
  
  void OpenALSoundRenderer::Sync(bool sync)
  {
  	if(sync)
  	{
  		if(SfxGroup.Size() > 0)
  		{
  			alSourcePausev(SfxGroup.Size(), &SfxGroup[0]);
  			getALError();
  			PurgeStoppedSources();
  		}
  	}
  	else
  	{
  		// Might already be something to handle this; basically, get a vector
  		// of all values in SfxGroup that are not also in PausableSfx (when
  		// SFXPaused is non-0).
  		TArray<ALuint> toplay = SfxGroup;
  		if(SFXPaused)
  		{
  			uint32_t i = 0;
  			while(i < toplay.Size())
  			{
  				uint32_t p = PausableSfx.Find(toplay[i]);
  				if(p < PausableSfx.Size())
  					toplay.Delete(i);
  				else
  					i++;
  			}
  		}
  		if(toplay.Size() > 0)
  		{
  			alSourcePlayv(toplay.Size(), &toplay[0]);
  			getALError();
  		}
  	}
  }
  
  void OpenALSoundRenderer::UpdateSoundParams3D(SoundListener *listener, FISoundChannel *chan, bool areasound, const FVector3 &pos, const FVector3 &vel)
  {
  	if(chan == NULL || chan->SysChannel == NULL)
  		return;
  
  	FVector3 dir = pos - listener->position;
  	chan->DistanceSqr = (float)dir.LengthSquared();
  
  	if(chan->ManualRolloff)
  	{
  		if(!AL.EXT_SOURCE_RADIUS && areasound &&
  		   chan->DistanceSqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
  		{
  			FVector3 amb(0.f, !(dir.Y>=0.f) ? -1.f : 1.f, 0.f);
  			float a = sqrtf(chan->DistanceSqr) / AREA_SOUND_RADIUS;
  			dir = amb + (dir-amb)*a;
  		}
  		if(dir.DoesNotApproximatelyEqual(FVector3(0.f, 0.f, 0.f)))
  		{
  			float gain = GetRolloff(&chan->Rolloff, sqrtf(chan->DistanceSqr)*chan->DistanceScale);
  			dir.MakeResize((gain > 0.00001f) ? 1.f/gain : 100000.f);
  		}
  	}
  	else if(!AL.EXT_SOURCE_RADIUS && areasound &&
  			chan->DistanceSqr < AREA_SOUND_RADIUS*AREA_SOUND_RADIUS)
  	{
  		float mindist = chan->Rolloff.MinDistance / chan->DistanceScale;
  		FVector3 amb(0.f, !(dir.Y>=0.f) ? -mindist : mindist, 0.f);
  		float a = sqrtf(chan->DistanceSqr) / AREA_SOUND_RADIUS;
  		dir = amb + (dir-amb)*a;
  	}
  	dir += listener->position;
  
  	alDeferUpdatesSOFT();
  	ALuint source = GET_PTRID(chan->SysChannel);
  
  	if(chan->DistanceSqr < (0.0004f*0.0004f))
  	{
  		alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE);
  		alSource3f(source, AL_POSITION, 0.f, 0.f, 0.f);
  	}
  	else
  	{
  		alSourcei(source, AL_SOURCE_RELATIVE, AL_FALSE);
  		alSource3f(source, AL_POSITION, dir[0], dir[1], -dir[2]);
  	}
  	alSource3f(source, AL_VELOCITY, vel[0], vel[1], -vel[2]);
  	getALError();
  }
  
  void OpenALSoundRenderer::UpdateListener(SoundListener *listener)
  {
  	if(!listener->valid)
  		return;
  
  	alDeferUpdatesSOFT();
  
  	float angle = listener->angle;
  	ALfloat orient[6];
  	// forward
  	orient[0] = cosf(angle);
  	orient[1] = 0.f;
  	orient[2] = -sinf(angle);
  	// up
  	orient[3] = 0.f;
  	orient[4] = 1.f;
  	orient[5] = 0.f;
  
  	alListenerfv(AL_ORIENTATION, orient);
  	alListener3f(AL_POSITION, listener->position.X,
  	                          listener->position.Y,
  	                         -listener->position.Z);
  	alListener3f(AL_VELOCITY, listener->velocity.X,
  	                          listener->velocity.Y,
  	                         -listener->velocity.Z);
  	getALError();
  
  	const ReverbContainer *env = ForcedEnvironment;
  	if(!env)
  	{
  		env = listener->Environment;
  		if(!env)
  			env = DefaultEnvironments[0];
  	}
  	if(env != PrevEnvironment || env->Modified)
  	{
  		PrevEnvironment = env;
  		DPrintf(DMSG_NOTIFY, "Reverb Environment %s\n", env->Name);
  
  		if(EnvSlot != 0)
  			LoadReverb(env);
  
  		const_cast<ReverbContainer*>(env)->Modified = false;
  	}
  
  	// NOTE: Moving into and out of water will undo pitch variations on sounds.
  	if(listener->underwater || env->SoftwareWater)
  	{
  		if(!WasInWater)
  		{
  			WasInWater = true;
  
  			if(EnvSlot != 0 && *snd_waterreverb)
  			{
  				// Find the "Underwater" reverb environment
  				env = S_FindEnvironment(0x1600);
  				LoadReverb(env ? env : DefaultEnvironments[0]);
  
  				alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 1.f);
  				alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 0.125f);
  				alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.f);
  				alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f);
  
  				// Apply the updated filters on the sources
  				for(uint32_t i = 0;i < ReverbSfx.Size();++i)
  				{
  					alSourcei(ReverbSfx[i], AL_DIRECT_FILTER, EnvFilters[0]);
  					alSource3i(ReverbSfx[i], AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
  				}
  			}
  
  			for(uint32_t i = 0;i < ReverbSfx.Size();++i)
  				alSourcef(ReverbSfx[i], AL_PITCH, PITCH_MULT);
  			getALError();
  		}
  	}
  	else if(WasInWater)
  	{
  		WasInWater = false;
  
  		if(EnvSlot != 0)
  		{
  			LoadReverb(env);
  
  			alFilterf(EnvFilters[0], AL_LOWPASS_GAIN, 1.f);
  			alFilterf(EnvFilters[0], AL_LOWPASS_GAINHF, 1.f);
  			alFilterf(EnvFilters[1], AL_LOWPASS_GAIN, 1.f);
  			alFilterf(EnvFilters[1], AL_LOWPASS_GAINHF, 1.f);
  			for(uint32_t i = 0;i < ReverbSfx.Size();++i)
  			{
  				alSourcei(ReverbSfx[i], AL_DIRECT_FILTER, EnvFilters[0]);
  				alSource3i(ReverbSfx[i], AL_AUXILIARY_SEND_FILTER, EnvSlot, 0, EnvFilters[1]);
  			}
  		}
  
  		for(uint32_t i = 0;i < ReverbSfx.Size();++i)
  			alSourcef(ReverbSfx[i], AL_PITCH, 1.f);
  		getALError();
  	}
  }
  
  void OpenALSoundRenderer::UpdateSounds()
  {
  	alProcessUpdatesSOFT();
  
  	if(!FadingSources.empty())
  	{
  		auto cur_time = std::chrono::steady_clock::now().time_since_epoch();
  		for(auto iter = FadingSources.begin();iter != FadingSources.end();)
  		{
  			auto time_diff = std::chrono::duration_cast<std::chrono::milliseconds>(cur_time -
  				std::chrono::steady_clock::time_point::duration(iter->second));
  			if(time_diff.count() >= UpdateTimeMS)
  			{
  				FreeSource(iter->first);
  				iter = FadingSources.erase(iter);
  			}
  			else
  				++iter;
  		}
  	}
  
  	if(ALC.EXT_disconnect)
  	{
  		ALCint connected = ALC_TRUE;
  		alcGetIntegerv(Device, ALC_CONNECTED, 1, &connected);
  		if(connected == ALC_FALSE)
  		{
  			Printf("Sound device disconnected; restarting...\n");
  			static char snd_reset[] = "snd_reset";
  			AddCommandString(snd_reset);
  			return;
  		}
  	}
  
  	PurgeStoppedSources();
  }
  
  bool OpenALSoundRenderer::IsValid()
  {
  	return Device != NULL;
  }
  
  void OpenALSoundRenderer::MarkStartTime(FISoundChannel *chan)
  {
  	// FIXME: Get current time (preferably from the audio clock, but the system
  	// time will have to do)
  	chan->StartTime.AsOne = std::chrono::steady_clock::now().time_since_epoch().count();
  }
  
  float OpenALSoundRenderer::GetAudibility(FISoundChannel *chan)
  {
  	if(chan == NULL || chan->SysChannel == NULL)
  		return 0.f;
  
  	ALuint source = GET_PTRID(chan->SysChannel);
  	ALfloat volume = 0.f;
  
  	alGetSourcef(source, AL_GAIN, &volume);
  	getALError();
  
  	volume *= GetRolloff(&chan->Rolloff, sqrtf(chan->DistanceSqr) * chan->DistanceScale);
  	return volume;
  }
  
  
  void OpenALSoundRenderer::PrintStatus()
  {
  	Printf("Output device: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_DEVICE_SPECIFIER));
  	getALCError(Device);
  
  	ALCint frequency, major, minor, mono, stereo;
  	alcGetIntegerv(Device, ALC_FREQUENCY, 1, &frequency);
  	alcGetIntegerv(Device, ALC_MAJOR_VERSION, 1, &major);
  	alcGetIntegerv(Device, ALC_MINOR_VERSION, 1, &minor);
  	alcGetIntegerv(Device, ALC_MONO_SOURCES, 1, &mono);
  	alcGetIntegerv(Device, ALC_STEREO_SOURCES, 1, &stereo);
  	if(getALCError(Device) == AL_NO_ERROR)
  	{
  		Printf("Device sample rate: " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL"hz\n", frequency);
  		Printf("ALC Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
  		Printf("ALC Extensions: " TEXTCOLOR_ORANGE"%s\n", alcGetString(Device, ALC_EXTENSIONS));
  		Printf("Available sources: " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" (" TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" mono, " TEXTCOLOR_BLUE"%d" TEXTCOLOR_NORMAL" stereo)\n", mono+stereo, mono, stereo);
  	}
  	if(!alcIsExtensionPresent(Device, "ALC_EXT_EFX"))
  		Printf("EFX not found\n");
  	else
  	{
  		ALCint sends;
  		alcGetIntegerv(Device, ALC_EFX_MAJOR_VERSION, 1, &major);
  		alcGetIntegerv(Device, ALC_EFX_MINOR_VERSION, 1, &minor);
  		alcGetIntegerv(Device, ALC_MAX_AUXILIARY_SENDS, 1, &sends);
  		if(getALCError(Device) == AL_NO_ERROR)
  		{
  			Printf("EFX Version: " TEXTCOLOR_BLUE"%d.%d\n", major, minor);
  			Printf("Auxiliary sends: " TEXTCOLOR_BLUE"%d\n", sends);
  		}
  	}
  	Printf("Vendor: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VENDOR));
  	Printf("Renderer: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_RENDERER));
  	Printf("Version: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_VERSION));
  	Printf("Extensions: " TEXTCOLOR_ORANGE"%s\n", alGetString(AL_EXTENSIONS));
  	getALError();
  }
  
  FString OpenALSoundRenderer::GatherStats()
  {
  	FString out;
  
  	ALCint refresh = 1;
  	alcGetIntegerv(Device, ALC_REFRESH, 1, &refresh);
  	getALCError(Device);
  
  	uint32_t total = Sources.Size();
  	uint32_t used = SfxGroup.Size()+Streams.Size();
  	uint32_t unused = FreeSfx.Size();
  
  	out.Format("%u sources (" TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" active, " TEXTCOLOR_YELLOW"%u" TEXTCOLOR_NORMAL" free), Update interval: " TEXTCOLOR_YELLOW"%.1f" TEXTCOLOR_NORMAL"ms",
  			   total, used, unused, 1000.f/static_cast<float>(refresh));
  	return out;
  }
  
  void OpenALSoundRenderer::PrintDriversList()
  {
  	const ALCchar *drivers = (alcIsExtensionPresent(NULL, "ALC_ENUMERATE_ALL_EXT") ?
  							  alcGetString(NULL, ALC_ALL_DEVICES_SPECIFIER) :
  							  alcGetString(NULL, ALC_DEVICE_SPECIFIER));
  	if(drivers == NULL)
  	{
  		Printf(TEXTCOLOR_YELLOW"Failed to retrieve device list: %s\n", alcGetString(NULL, alcGetError(NULL)));
  		return;
  	}
  
  	const ALCchar *current = NULL;
  	if(alcIsExtensionPresent(Device, "ALC_ENUMERATE_ALL_EXT"))
  		current = alcGetString(Device, ALC_ALL_DEVICES_SPECIFIER);
  	if(alcGetError(Device) != ALC_NO_ERROR || !current)
  		current = alcGetString(Device, ALC_DEVICE_SPECIFIER);
  	if(current == NULL)
  	{
  		Printf(TEXTCOLOR_YELLOW"Failed to retrieve device name: %s\n", alcGetString(Device, alcGetError(Device)));
  		return;
  	}
  
  	Printf("%c%s%2d. %s\n", ' ', ((strcmp(snd_aldevice, "Default") == 0) ? TEXTCOLOR_BOLD : ""), 0,
  		   "Default");
  	for(int i = 1;*drivers;i++)
  	{
  		Printf("%c%s%2d. %s\n", ((strcmp(current, drivers)==0) ? '*' : ' '),
  			   ((strcmp(*snd_aldevice, drivers)==0) ? TEXTCOLOR_BOLD : ""), i,
  			   drivers);
  		drivers += strlen(drivers)+1;
  	}
  }
  
  void OpenALSoundRenderer::PurgeStoppedSources()
  {
  	// Release channels that are stopped
  	for(uint32_t i = 0;i < SfxGroup.Size();++i)
  	{
  		ALuint src = SfxGroup[i];
  		ALint state = AL_INITIAL;
  		alGetSourcei(src, AL_SOURCE_STATE, &state);
  		if(state == AL_INITIAL || state == AL_PLAYING || state == AL_PAUSED)
  			continue;
  
  		FSoundChan *schan = Channels;
  		while(schan)
  		{
  			if(schan->SysChannel != NULL && src == GET_PTRID(schan->SysChannel))
  			{
  				ForceStopChannel(schan);
  				break;
  			}
  			schan = schan->NextChan;
  		}
  	}
  	getALError();
  }
  
  void OpenALSoundRenderer::LoadReverb(const ReverbContainer *env)
  {
  	ALuint *envReverb = EnvEffects.CheckKey(env->ID);
  	bool doLoad = (env->Modified || !envReverb);
  
  	if(!envReverb)
  	{
  		bool ok = false;
  
  		envReverb = &EnvEffects.Insert(env->ID, 0);
  		alGenEffects(1, envReverb);
  		if(getALError() == AL_NO_ERROR)
  		{
  			alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_EAXREVERB);
  			ok = (alGetError() == AL_NO_ERROR);
  			if(!ok)
  			{
  				alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_REVERB);
  				ok = (alGetError() == AL_NO_ERROR);
  			}
  			if(!ok)
  			{
  				alEffecti(*envReverb, AL_EFFECT_TYPE, AL_EFFECT_NULL);
  				ok = (alGetError() == AL_NO_ERROR);
  			}
  			if(!ok)
  			{
  				alDeleteEffects(1, envReverb);
  				getALError();
  			}
  		}
  		if(!ok)
  		{
  			*envReverb = 0;
  			doLoad = false;
  		}
  	}
  
  	if(doLoad)
  	{
  		const REVERB_PROPERTIES &props = env->Properties;
  		ALint type = AL_EFFECT_NULL;
  
  		alGetEffecti(*envReverb, AL_EFFECT_TYPE, &type);
  #define mB2Gain(x) ((float)pow(10., (x)/2000.))
  		if(type == AL_EFFECT_EAXREVERB)
  		{
  			ALfloat reflectpan[3] = { props.ReflectionsPan0,
  			                          props.ReflectionsPan1,
  			                          props.ReflectionsPan2 };
  			ALfloat latepan[3] = { props.ReverbPan0, props.ReverbPan1,
  			                       props.ReverbPan2 };
  #undef SETPARAM
  #define SETPARAM(e,t,v) alEffectf((e), AL_EAXREVERB_##t, clamp((v), AL_EAXREVERB_MIN_##t, AL_EAXREVERB_MAX_##t))
  			SETPARAM(*envReverb, DIFFUSION, props.EnvDiffusion);
  			SETPARAM(*envReverb, DENSITY, powf(props.EnvSize, 3.0f) * 0.0625f);
  			SETPARAM(*envReverb, GAIN, mB2Gain(props.Room));
  			SETPARAM(*envReverb, GAINHF, mB2Gain(props.RoomHF));
  			SETPARAM(*envReverb, GAINLF, mB2Gain(props.RoomLF));
  			SETPARAM(*envReverb, DECAY_TIME, props.DecayTime);
  			SETPARAM(*envReverb, DECAY_HFRATIO, props.DecayHFRatio);
  			SETPARAM(*envReverb, DECAY_LFRATIO, props.DecayLFRatio);
  			SETPARAM(*envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections));
  			SETPARAM(*envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay);
  			alEffectfv(*envReverb, AL_EAXREVERB_REFLECTIONS_PAN, reflectpan);
  			SETPARAM(*envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb));
  			SETPARAM(*envReverb, LATE_REVERB_DELAY, props.ReverbDelay);
  			alEffectfv(*envReverb, AL_EAXREVERB_LATE_REVERB_PAN, latepan);
  			SETPARAM(*envReverb, ECHO_TIME, props.EchoTime);
  			SETPARAM(*envReverb, ECHO_DEPTH, props.EchoDepth);
  			SETPARAM(*envReverb, MODULATION_TIME, props.ModulationTime);
  			SETPARAM(*envReverb, MODULATION_DEPTH, props.ModulationDepth);
  			SETPARAM(*envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF));
  			SETPARAM(*envReverb, HFREFERENCE, props.HFReference);
  			SETPARAM(*envReverb, LFREFERENCE, props.LFReference);
  			SETPARAM(*envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor);
  			alEffecti(*envReverb, AL_EAXREVERB_DECAY_HFLIMIT,
  					  (props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE);
  #undef SETPARAM
  		}
  		else if(type == AL_EFFECT_REVERB)
  		{
  #define SETPARAM(e,t,v) alEffectf((e), AL_REVERB_##t, clamp((v), AL_REVERB_MIN_##t, AL_REVERB_MAX_##t))
  			SETPARAM(*envReverb, DIFFUSION, props.EnvDiffusion);
  			SETPARAM(*envReverb, DENSITY, powf(props.EnvSize, 3.0f) * 0.0625f);
  			SETPARAM(*envReverb, GAIN, mB2Gain(props.Room));
  			SETPARAM(*envReverb, GAINHF, mB2Gain(props.RoomHF));
  			SETPARAM(*envReverb, DECAY_TIME, props.DecayTime);
  			SETPARAM(*envReverb, DECAY_HFRATIO, props.DecayHFRatio);
  			SETPARAM(*envReverb, REFLECTIONS_GAIN, mB2Gain(props.Reflections));
  			SETPARAM(*envReverb, REFLECTIONS_DELAY, props.ReflectionsDelay);
  			SETPARAM(*envReverb, LATE_REVERB_GAIN, mB2Gain(props.Reverb));
  			SETPARAM(*envReverb, LATE_REVERB_DELAY, props.ReverbDelay);
  			SETPARAM(*envReverb, AIR_ABSORPTION_GAINHF, mB2Gain(props.AirAbsorptionHF));
  			SETPARAM(*envReverb, ROOM_ROLLOFF_FACTOR, props.RoomRolloffFactor);
  			alEffecti(*envReverb, AL_REVERB_DECAY_HFLIMIT,
  					  (props.Flags&REVERB_FLAGS_DECAYHFLIMIT)?AL_TRUE:AL_FALSE);
  #undef SETPARAM
  		}
  #undef mB2Gain
  	}
  
  	alAuxiliaryEffectSloti(EnvSlot, AL_EFFECTSLOT_EFFECT, *envReverb);
  	getALError();
  }
  
  FSoundChan *OpenALSoundRenderer::FindLowestChannel()
  {
  	FSoundChan *schan = Channels;
  	FSoundChan *lowest = NULL;
  	while(schan)
  	{
  		if(schan->SysChannel != NULL)
  		{
  			if(!lowest || schan->Priority < lowest->Priority ||
  			   (schan->Priority == lowest->Priority &&
  				schan->DistanceSqr > lowest->DistanceSqr))
  				lowest = schan;
  		}
  		schan = schan->NextChan;
  	}
  	return lowest;
  }
  
  #endif // NO_OPENAL
  